1 2 3 4 5 6 7
Setting up your Compiler Our Generic Windows Skeleton A Simple Cube Matrices Wazoo of Borg FullScreen Ahead Mr. Crusher On screen. Magnify.

 

 

 

Tutorial #3: A Simple Cube
Erik Yuzwa


Version History
Date Version Description Font Color
July 16th, 2001 1.0 General Public Release White
Sept. 10th, 2001 1.5 Updated code due to WIN32 skeleton update Red

Welcome to our first tutorial on DirectX8! Everyone seems to have their own version of the "Hello World" program, and I am no different. However, because I like to fool around with graphics and games, my "Hello World" is a simple spinning cube

DirectX8 provides a swak load of new features for the game programmer, but there's also some changes to the DirectX line:
  1. DirectDraw is no more..in order to do ANY 2D graphics, you simply need to remember (or realize) that 2D is merely a subset of 3D. Meaning all you have to do is to draw texture maps onto Direct3D primitives to handle your 2D work. IF you have no idea what I just said, have patience and we'll get there with our tutorials..
  2. The 3D initialization code has been reduced to virtually one function! YAY! Up until DirectX7 we needed reams and reams of initialization and error checking routines just to get started with Direct3D..yuck!

We'll first create a new project, using the steps from our Tutorial #0

Okay we are going to be working from our basic Windows skeleton here so make sure you back up your original and only modify a copy..making it easier to reuse the skeleton in later applications..
We are going to declare some globals for our program at the top of your main program file
//in addition to our <windows.h> include file, add the rest
#include <mmsystem.h> // this handles our timing code
#include <d3dx8.h>    // this is for our Direct3D8 object

#pragma comment(lib, "dxguid.lib")//This takes care of our GUID information
#pragma comment(lib, "d3dx8.lib")//This allows us to use the Direct3D8 utility functions
#pragma comment(lib, "d3d8.lib")//This allows us to use the Direct3D8 functions
#pragma comment(lib, "winmm.lib")//This allows us to use the timing library to spin our cube

struct MYVERTEX
{
    FLOAT x, y, z;  // The vertex co-ordinates
    D3DCOLOR color; // The vertex color
};

#define D3DFVF_MYVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)

// we are defining a vertex definition for Direct3D. This format is very flexible
// and allows us to define virtually any type of vertex for our application. This one
// is simple, as we need only to store the x,y,z coordinates of each vertex as well
// as the color

//our indices array is for easier and more efficient primitive rendering..rather than
// define vertices for each object, we can define our vertices in ONE spot, and then
//tell our D3D object in which order to draw our vertices with this array
//NOTE: I grouped them into three's for easier readability. Because it's faster to
//render each primitive in triangles, we have to split each face of our cube into two
//


// application globals
LPDIRECT3D8             g_pD3D       = NULL; // our Direct3D object
LPDIRECT3DDEVICE8       g_pd3dDevice = NULL; // our Direct3D device object
LPDIRECT3DINDEXBUFFER8  g_pCubeIB    = NULL; // our index buffer to hold our indices array
LPDIRECT3DVERTEXBUFFER8 g_pCubeVB    = NULL; // our vertex buffer to hold our cube's vertices


Now we'll cover the initialization routine. We'll initialize our Direct3D object, device and create and fill our vertex and index buffers..
Primitive: This is the base rendering object of Direct3D and OpenGL...the triangle is the easiest and quickest primitive to render, as everything can be broken down into triangles. Vertex Buffer: A storage system to store vertices for our primitives. Index Buffer: A storage system to store the order of primitives that we render from our Vertex Buffer

////////////////////////////////////////////////////////
// GAME_INIT
// This module handles the initialization of our game
//

HRESULT Game_Init(HWND hWnd, HINSTANCE hInstance){

	// Here's is ALL we need to initialize and create our Direct3D object
	// Remember DX7?? That was reams and reams of code
	
	if( NULL == ( g_pD3D = Direct3DCreate8( D3D_SDK_VERSION ) ) )
		return E_FAIL;

	// Let's get the display mode from our Direct3D object
	D3DDISPLAYMODE d3ddm;
	if( FAILED( g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm ) ) )
		return E_FAIL;

	// Here is where we set the display properties of our application...for
	// now we're only going to do a windowed application, as it is a bit easier
	// to exit the app
	D3DPRESENT_PARAMETERS d3dpp;
	ZeroMemory( &d3dpp, sizeof(d3dpp) );
	d3dpp.Windowed   = TRUE;
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	d3dpp.BackBufferFormat = d3ddm.Format;

	// Let's create our Direct3D Device object using our Direct3D object, pointer
	// to our main window handle and passing it our display parameters
	if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT,
				  D3DDEVTYPE_HAL, g_hWnd,
                                  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                  &d3dpp,
                                  &g_pd3dDevice ) ) )
		return E_FAIL; //we failed so exit

	// Turn off culling, so we see the front and back of the triangle
    	g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );

    	// Turn off D3D lighting, since we are providing our own vertex colors
    	g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );

	// Turn on the fillmode for our cube, to make it look more solid
	g_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );

	// Now we're just going to create our vertex and index buffers
	// as well as fill them with our information
	MYVERTEX vCubeVertices[] =
	{
    		{ 0.0f, 0.0f, 0.0f, D3DCOLOR_RGBA(255, 0, 0, 255), }, // x, y, z, color point 0
    		{ 0.0f, 1.0f, 0.0f, D3DCOLOR_RGBA(255, 255, 0, 255), }, //point 1
    		{ 1.0f, 0.0f, 0.0f, D3DCOLOR_RGBA(255, 0, 0, 255), }, //point 2
		{ 1.0f, 1.0f, 0.0f, D3DCOLOR_RGBA(0, 0, 255, 255), }, //point 3
		{ 1.0f, 0.0f, 1.0f, D3DCOLOR_RGBA(255, 255, 255, 255), }, // x, y, z, color point 4
    		{ 1.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(255, 0, 0, 255), }, //point 5
    		{ 0.0f, 0.0f, 1.0f, D3DCOLOR_RGBA(0, 0, 0, 255), }, //point 6
		{ 0.0f, 1.0f, 1.0f, D3DCOLOR_RGBA(255, 0, 0, 255), } //point 7
	};

	// now create our vertex buffer object to be able to hold our 8 vertexes
	if( FAILED( g_pd3dDevice->CreateVertexBuffer( 8 * sizeof(MYVERTEX), //allocate memory
                                              	      0 ,// Usage
						      D3DFVF_MYVERTEX, // format of MYVERTEX
                                              	      D3DPOOL_DEFAULT, // default D3DPOOL values
						      &g_pCubeVB ) ) ) // pointer to our Vertex Buffer
    	{return E_FAIL;}
    	// now that we've created our Vertex Buffer, we have to fill it with our vertex information
	// that we created above. Before writing to our Vertex Buffer, we have to lock it down to guarantee
	// we are the only process using it!
	VOID* pVertices;
	if( FAILED( g_pCubeVB->Lock( 0, sizeof(vCubeVertices), (BYTE**)&pVertices, 0 ) ) )
    		{return E_FAIL;}

	// now copy our vertex information into the vertex buffer
	memcpy( pVertices, vCubeVertices, sizeof(vCubeVertices) );

	//we're done, so we MUST unlock our vertex buffer
	g_pCubeVB->Unlock();

	// Now we create an index buffer to store our order and rendering
	// information for each vertex
	if( FAILED( g_pd3dDevice->CreateIndexBuffer( 36 * sizeof(WORD), // there's 36 triangles in a 6-sided cube
                                            D3DUSAGE_WRITEONLY,
                                            D3DFMT_INDEX16,
                                            D3DPOOL_DEFAULT,
                                            &g_pCubeIB ) ) )            // pointer to our Index Buffer
    	{return E_FAIL;}

	// Now that we've created our Index Buffer, we have to lock it down in order
	// to fill it with our information, just like the Vertex Buffer
	WORD cube_indices[36] = {0, 1, 2,
			   2, 1, 3,
			   2, 3, 4,
			   4, 3, 5,
			   4, 5, 6,
			   6, 5, 7,
		     	   6, 7, 0,
			   0, 7, 1,
			   1, 7, 3,
			   3, 7, 5,
			   0, 2, 6,
			   2, 4, 6};


	VOID* pIndices;
	if( FAILED( g_pCubeIB->Lock( 0,                 //Fill from start of the buffer.
                      		     sizeof(cube_indices), // Size of the data to load.
                      		     (BYTE**)&pIndices, // Returned index data.
                      		     0 ) ) )            // Send default flags to the lock.
    	return E_FAIL;

	// copy over our index information into our index buffer
	memcpy( pIndices, cube_indices, sizeof(cube_indices) );

	// now unlock the index buffer so that we can use it
	g_pCubeIB->Unlock();

	return S_OK; //no error YAY!

}//end Game_Init


Okay here we did quite a bit of code...hehehe. We initialized our Direct3D object, using the ONE function call to create it.. then we grabbed our Device display mode. For now we only want to create a windowed application, which spans a 640x480 window. This way, we can quickly create a program without worrying about user input for now.

We then created our Vertex Buffer which will be stored in memory by DirectX8. We use the vertex buffer to fill it with our vertices of our cube object. But before we can use it, we have to fill the buffer with our desired vertices. This requires us to Lock the Buffer down in order to ensure we are the only process updating it. Then we do a memcpy to copy the vertices from our array to the Vertex Buffer. Important to remember is that we cannot use the Vertex Buffer until we Unlock it once we are done.

Next, we created our Index Buffer, which is sort of the same principle as the Vertex Buffer, however it tells the rendering device which order we would like to draw our vertices in...that way we can use a minimal amount of vertices, while at the same time constructing many complete primitives (ie. cones, cubes, etc), with different information in the Index Buffer.
Again we have to lock down the Buffer before we can fill it with our Index information. A quick memcpy later, and we are done! Don't forget to Unlock it again...


////////////////////////////////////////////////////////
// GAME_FRAME
// This module handles the main loop of our game
//
void Game_Run(void *params) {

	// This function is responsible for updating each frame
	// For our world matrix, we will just rotate the object about the y-axis.
    	// DO NOT WORRY if you don't understand the role of matrices just yet,
    	// I'll explain it more in detail for our next tutorial
    	//
    	D3DXMATRIX matWorld;
    	D3DXMatrixRotationY( &matWorld, timeGetTime()/150.0f );
    	g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

    	// Set up our view matrix. A view matrix can be defined given an eye point,
    	// a point to lookat, and a direction for which way is up. Here, we set the
    	// eye five units back along the z-axis and up three units, look at the
    	// origin, and define "up" to be in the y-direction.
    	//
    	D3DXMATRIX matView;
    	D3DXMatrixLookAtLH( &matView,
			    &D3DXVECTOR3( 0.0f, 3.0f, -5.0f ),
			    &D3DXVECTOR3( 0.0f, 0.0f, 0.0f ),
			    &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );

	g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

    	// For the projection matrix, we set up a perspective transform (which
    	// transforms geometry from 3D view space to 2D viewport space, with
    	// a perspective divide making objects smaller in the distance). To build
    	// a perpsective transform, we need the field of view (1/4 pi is common),
    	// the aspect ratio, and the near and far clipping planes (which define at
    	// what distances geometry should be no longer be rendered).
    	//
    	D3DXMATRIX matProj;
    	D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f );
    	g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

    	// This function is responsible for rendering our frame to the screen
	// Clear the back buffer to a blue color
	g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );

	// Begin the scene. We have to use this command before doing any kind of
	// rendering information
	g_pd3dDevice->BeginScene();

	// Rendering of scene objects happens here.

	// Set the default vertex shader to our #defined vertex
	g_pd3dDevice->SetVertexShader( D3DFVF_MYVERTEX );

	// Set the location source of our vertex input stream
	g_pd3dDevice->SetStreamSource( 0, g_pCubeVB, sizeof(MYVERTEX) );

	// Set the location of our index information to our Index Buffer
	g_pd3dDevice->SetIndices( g_pCubeIB, 0 );

	// this is where we go through the vertex buffer, processing the information
	// that we've put into the Index Buffer.
	// First Param - our primitive drawing style
	// Second Param - our starting vertex in our vertex buffer
	// Third Param - our number of vertexes to draw
	// Fourth Param - our starting index in the Index Buffer
	// Fifth Param - our number of Primitives being rendered...(ie. number of complete triangles)
	//
	g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
					   0,
					   8,
					   0,
					   12);


	// End the scene, it's time to render
	g_pd3dDevice->EndScene();

	//blast everything to the screen...replaces the DDraw->Flip command
	g_pd3dDevice->Present( NULL, NULL, NULL, NULL );

}//end Game_Frame


////////////////////////////////////////////////////////
// GAME_SHUTDOWN
// This module handles the shutdown and cleanup of our game
//
void Game_Shutdown(void *params) {

	// if our cube Index Buffer still exists, then release it's memory
	if( g_pCubeIB ){
		g_pCubeIB->Release();
		g_pCubeIB = NULL;
	}

	// if our cube Vertex Buffer still exists, then release it's memory
	if( g_pCubeVB ) {
		g_pCubeVB->Release();
		g_pCubeVB = NULL;
	}

	// make sure we release our Direct3D Device
	if( g_pd3dDevice != NULL)
        	g_pd3dDevice->Release();

    	// make sure we release our Direct3D Object
    	if( g_pD3D != NULL)
        	g_pD3D->Release();

}//end Game_Shutdown

////////////////////////////////////////////////////////


Okay, we'll that about wraps it up for God...hhhehehe. Okay obsure reference to The Hitchhikers Guide to the Galaxy, but anyways all we just did was to cleanup our used memory for our application..

Well that about wraps it up for this tutorial. We've learned how to create and initialize our Direct3D object, create and use vertex and index buffers, as well as how to manipulate the matrix views..

[End of Tutorial]

Back to the Index

 


 

[top]

 
 
© 2003, Silver.
Wind, two Tears, and a Funeral...