News
DirectX
Links
Contact Me

In Association with Amazon.com
In Association with Amazon.ca

$5 via PayPal


Direct3D Lesson 5



Direct3D Lesson 5:Solid Objects
The first thing we have to change is the definition of our vertices. It would have been simpler to algorithmically generate the cube, but I think it's more illustrative to have the vertices written out at this point. Here's our new definition:
//Here we have the vertices for our pyramid, followed by those for our cube.
//These coordinates are in model space, with 0,0,0 being at their center.
//The pyramid will have each vertex set to a different colour to show how
//the colours will nicely blend, while the cube will have each face set to
//a solid colour.
//The pyramid will be drawn as a Triangle Fan.  Triangle Fan's are special because
//the first vertex drawn becomes part of EVERY triangle drawn afterwards.  Fans are
//fast and efficient, but can only be used in very specific circumstances. In the case
//of a pyramid, the top vertex is shared by all the sides (we're not drawing a bottom
//since it wouldn't be seen anyway).
my_vertex g_vertices[] ={
   {  0.0f,  1.0f, 0.0f, 0x00FF0000 }, // top corner
   {  1.0f, -1.0f,-1.0f, 0x0000FF00 }, // right, front
   { -1.0f, -1.0f,-1.0f, 0x000000FF }, // left, front

   { -1.0f, -1.0f, 1.0f, 0x0000FF00 }, // back, left

   {  1.0f, -1.0f, 1.0f, 0x000000FF }, // back, right
   {  1.0f, -1.0f,-1.0f, 0x0000FF00 }, // right, front (repeated to close the pyramid)

//The cube will be drawn as several (12 in fact, 2 triangles per face, 6 faces) triangles
//in a triangle list.  No vertices are shared, every triangle must be fully specified.
//Even though the right side can't be seen, we'll draw it anyway.
   { -1.0f, -1.0f, -1.0f, 0x000000FF },  //Front face
   { -1.0f,  1.0f, -1.0f, 0x000000FF },
   {  1.0f,  1.0f, -1.0f, 0x000000FF },
   {  1.0f,  1.0f, -1.0f, 0x000000FF },
   {  1.0f, -1.0f, -1.0f, 0x000000FF },
   { -1.0f, -1.0f, -1.0f, 0x000000FF },

   {  1.0f, -1.0f,  1.0f, 0x0000FF00 },  //Back face
   {  1.0f,  1.0f,  1.0f, 0x0000FF00 },
   { -1.0f,  1.0f,  1.0f, 0x0000FF00 },
   { -1.0f,  1.0f,  1.0f, 0x0000FF00 },
   { -1.0f, -1.0f,  1.0f, 0x0000FF00 },
   {  1.0f, -1.0f,  1.0f, 0x0000FF00 },


   { -1.0f,  1.0f, -1.0f, 0x00FF0000 },  //Top face
   { -1.0f,  1.0f,  1.0f, 0x00FF0000 },
   {  1.0f,  1.0f,  1.0f, 0x00FF0000 },
   {  1.0f,  1.0f,  1.0f, 0x00FF0000 },
   {  1.0f,  1.0f, -1.0f, 0x00FF0000 },
   { -1.0f,  1.0f, -1.0f, 0x00FF0000 },

   {  1.0f, -1.0f, -1.0f, 0x00FFFF00 },  //Bottom face
   {  1.0f, -1.0f,  1.0f, 0x00FFFF00 },
   { -1.0f, -1.0f,  1.0f, 0x00FFFF00 },
   { -1.0f, -1.0f,  1.0f, 0x00FFFF00 },
   { -1.0f, -1.0f, -1.0f, 0x00FFFF00 },
   {  1.0f, -1.0f, -1.0f, 0x00FFFF00 },

   { -1.0f, -1.0f,  1.0f, 0x00FF00FF },  //Left face
   { -1.0f,  1.0f,  1.0f, 0x00FF00FF },
   { -1.0f,  1.0f, -1.0f, 0x00FF00FF },
   { -1.0f,  1.0f, -1.0f, 0x00FF00FF },
   { -1.0f, -1.0f, -1.0f, 0x00FF00FF },
   { -1.0f, -1.0f,  1.0f, 0x00FF00FF },

   {  1.0f, -1.0f, -1.0f, 0x0000FFFF },  //Right face
   {  1.0f,  1.0f, -1.0f, 0x0000FFFF },
   {  1.0f,  1.0f,  1.0f, 0x0000FFFF },
   {  1.0f,  1.0f,  1.0f, 0x0000FFFF },
   {  1.0f, -1.0f,  1.0f, 0x0000FFFF },
   {  1.0f, -1.0f, -1.0f, 0x0000FFFF },

};

When modifying arrays (like our vertex array) it's easy to forget to update all the code that references the array. For example, while writing this tutorial I added a few vertices to the array but forgot to change the code that allocates the vertex buffer. I ended up stomping all over memory that wasn't mine. To make sure I didn't do that again, I added this little macro:
//This handy macro makes sure that we always reference the right number of
//vertices.  This way we always allocate a correctly sized vertex buffer.
#define NUM_VERTICES (sizeof(g_vertices)/sizeof(my_vertex))

Now that we're drawing real 3D objects, we need a Z Buffer. To tell Direct3D to allocate one we change the way our D3DPRESENT_PARAMETERS structure is filled in. Here's the part of the build_d3d function that fills in the D3DPRESENT_PARAMETERS. The new lines are in red.
   //Whether we're full-screen or windowed these are the same.
   d3dpp.SwapEffect     = D3DSWAPEFFECT_DISCARD; // Throw away previous frames, we don't need them
   d3dpp.hDeviceWindow  = g_main_window;  //This is our main (and only) window
   d3dpp.BackBufferCount= 1;  //We only need a single back buffer

   //We need a Z-Buffer so everything will be drawn properly
   d3dpp.EnableAutoDepthStencil = TRUE;
   d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

Previously we've turned off backface culling. This makes it easy to lazily put together vertices, but it's not a good idea in general. Culling can give an incredible speed increase, and in general it's a good idea. So we take out the line which turned off culling (it was in init_scene).

Now that we have a Z-Buffer, we have to make sure it's being used. Direct3D defaults to having the Z-Buffer enabled, but I like to set it anyway. That way if the default changes, my code will still work as expected. If you need something set a certain way, set it explicitly. To do this we add this small bit to our init_scene() function:
    // Turn on the zbuffer.  The ZBuffer makes sure that objects appear in the
    //proper order.  Without it we'd have to make sure we draw our triangles
    //in back-to-front order, with a ZBuffer we don't have to worry about it.
    g_d3d_device->SetRenderState( D3DRS_ZENABLE, TRUE );

After creating our 'clever' NUM_VERTICES macro, we should modify our CreateVertexBuffer() call (in init_scene) to use it.
   hr=g_d3d_device->CreateVertexBuffer(NUM_VERTICES*sizeof(my_vertex),
                                       D3DUSAGE_WRITEONLY,
                                       D3D8T_CUSTOMVERTEX,
                                       D3DPOOL_MANAGED,
                                       &g_vb);

Because we're using a Z-Buffer we also have to make a change to the way we clear our back buffer at the beginning of each frame. We don't want any old depth information left lying around on our buffer. So we make this minor change in our render() function:
   //Clear the buffer to our new colour and clear the ZBuffer
   g_d3d_device->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER , D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );

Since our 3D objects are bigger than our previous 2D objects, we move them farther to the side and push them deeper into the screen. We also have to change our DrawPrimitive call to use a Triangle Fan instead of a Triangle List.
      //Set up the rotation matrix for the triangle
      D3DXMatrixRotationY(&rot_matrix,rot_triangle);
      //Translate (move) it 1.5 units to the left, 1 unit into the screen
      D3DXMatrixTranslation(&trans_matrix,-1.5,0.0f,1.0f);
      //Combine the 2 matrices to get our final World Matrix
      D3DXMatrixMultiply(&matWorld,&rot_matrix,&trans_matrix);

      //Set our World Matrix
      g_d3d_device->SetTransform(D3DTS_WORLD,&matWorld );

      //After all that setup, actually drawing the triangle is pretty easy.
      //We tell it what we're giving it (a Triangle List), where it should
      //start reading (0, the beginning), and how many triangles we're drawing(1)
      g_d3d_device->DrawPrimitive(D3DPT_TRIANGLEFAN,0,4);


For our cube, we move it to the right and deeper, and call DrawPrimitive with a Triangle List.
      //Set up the rotation matrix for the square
      D3DXMatrixRotationX(&rot_matrix,rot_square);
      //Set up the World Matrix for the square
      D3DXMatrixTranslation(&trans_matrix,1.5,0.0f,1.0f);
      //Combine the 2 matrices to get our final World Matrix
      D3DXMatrixMultiply(&matWorld,&rot_matrix,&trans_matrix);

      g_d3d_device->SetTransform( D3DTS_WORLD, &matWorld );

      //Skip the first 6 vertices, they belong to our triangle
      //Then draw the next 12 triangles as a Triangle List
      g_d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST,6,12);