{m_rX – (m_rWidth / 2), m_rY – (m_rHeight / 2), m_rZ – (m_rDepth / 2), 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,}, //Vertex 18
{m_rX – (m_rWidth / 2), m_rY + (m_rHeight / 2), m_rZ – (m_rDepth / 2), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,}, //Vertex 19
//Bottom Face
{m_rX + (m_rWidth / 2), m_rY – (m_rHeight / 2), m_rZ – (m_rDepth / 2), 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,}, //Vertex 20
{m_rX + (m_rWidth / 2), m_rY – (m_rHeight / 2), m_rZ + (m_rDepth / 2), 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,}, //Vertex 21
{m_rX – (m_rWidth / 2), m_rY – (m_rHeight / 2), m_rZ – (m_rDepth / 2), 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,}, //Vertex 22
{m_rX – (m_rWidth / 2), m_rY – (m_rHeight / 2), m_rZ + (m_rDepth / 2), 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,}, //Vertex 23
};
//Get a pointer to the index buffer indices and lock the index buffer
m_pIndexBuffer->Lock(0, m_dwNumOfIndices * sizeof(WORD), (BYTE**)&pBufferIndices, D3DLOCK_READONLY);
//For each triangle, count the number of times each vertex is used and
//add together the normals of faces that share a vertex
for (i = 0; i < m_dwNumOfIndices; i += 3) {
dwVertex1 = pBufferIndices[i];
dwVertex2 = pBufferIndices[i + 1];
dwVertex3 = pBufferIndices[i + 2];
vNormal = GetTriangeNormal(&D3DXVECTOR3(cvVertices[dwVertex1].x, cvVertices[dwVertex1].y, cvVertices[dwVertex1].z), &D3DXVECTOR3(cvVertices[dwVertex2].x, cvVertices[dwVertex2].y, cvVertices[dwVertex2].z), &D3DXVECTOR3(cvVertices[dwVertex3].x, cvVertices[dwVertex3].y, cvVertices[dwVertex3].z));
pNumOfSharedPolygons[dwVertex1]++;
pNumOfSharedPolygons[dwVertex2]++;
pNumOfSharedPolygons[dwVertex3]++;
pSumVertexNormal[dwVertex1].x += vNormal.x;
pSumVertexNormal[dwVertex1].y += vNormal.y;
pSumVertexNormal[dwVertex1].z += vNormal.z;
pSumVertexNormal[dwVertex2].x += vNormal.x;
pSumVertexNormal[dwVertex2].y += vNormal.y;
pSumVertexNormal[dwVertex2].z += vNormal.z;
pSumVertexNormal[dwVertex3].x += vNormal.x;
pSumVertexNormal[dwVertex3].y += vNormal.y;
pSumVertexNormal[dwVertex3].z += vNormal.z;
}
//Unlock the index buffer
m_pIndexBuffer->Unlock();
//For each vertex, calculate and set the average normal
for (i = 0; i < m_dwNumOfVertices; i++) {
vNormal.x = pSumVertexNormal[i].x / pNumOfSharedPolygons[i];
vNormal.y = pSumVertexNormal[i].y / pNumOfSharedPolygons[i];
vNormal.z = pSumVertexNormal[i].z / pNumOfSharedPolygons[i];
D3DXVec3Normalize(&vNormal, &vNormal);
cvVertices[i].nx = vNormal.x;
cvVertices[i].ny = vNormal.y;
cvVertices[i].nz = vNormal.z;
}
//Get a pointer to the vertex buffer vertices and lock the vertex buffer
if (FAILED(m_pVertexBuffer->Lock(0, sizeof(cvVertices), (BYTE**)&pVertices, 0))) {
return false;
}
//Copy our stored vertices values into the vertex buffer
memcpy(pVertices, cvVertices, sizeof(cvVertices));
//Unlock the vertex buffer
m_pVertexBuffer->Unlock();
//Clean up
delete pNumOfSharedPolygons;
delete pSumVertexNormal;
pNumOfSharedPolygons = NULL;
pSumVertexNormal = NULL;
return true;
}
Step 3: Render
Our render function stays the same apart form two things, first of all, we need to tell DirectX that we want to render our polygons from an index buffer. We do this with a call to SetIndices, passing in the index buffer pointer that we want to use. Also, we need to use the DrawIndexedPrimitive method rather than DrawPrimitive to render our polygons.
//Select index buffer
m_pD3DDevice->SetIndices(m_pIndexBuffer, 0);
//Render polygons from index buffer
m_pD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, m_dwNumOfVertices, 0, m_dwNumOfPolygons);
To create our terrain, we will use the same techniques as we did to create our cube. The terrain will be made up from a 20×20 grid of quads (squares made up from 2 triangles). We will use an index buffer, which will reduce the number of vertices from 2400 to 441. I'm not going to post the code here because it is very similar to the code above. The only real differences are the way the index and vertex buffers are populated. You can download the full source code by clicking the "Download Source" link above. Fig 8.2 below shows our flat terrain grid. You can render your scene in "wireframe" mode with one simple call to SetRenderState which is also shown below.
//Set fill state. Possible values: D3DFILL_POINT, D3DFILL_WIREFRAME, D3DFILL_SOLID
m_pD3DDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
Fig 8.2
Now that we have a flat grid, we can turn this into a simple terrain by setting a random value for y for each vertex. I've done this for each vertex in the grid except the ones that are around the edge so that the four edges are all the same level. If you want to create a more detailed terrain, have a look at http://www.gameprogrammer.com/fractal.html where there is a tutorial on how to generate a random fractal terrain. Fig 8.3 below shows the grid with random y values.
Fig 8.3
The last thing to do is apply a grass texture to the terrain and set the render state to solid rather than wireframe. Once you have done this, you should end up with a scene that looks like the screenshot below.
In this tutorial we've look at index buffers and how they can make a massive saving on how many vertices need to be processed. In the next tutorial we will look at how to create other shapes such as spheres, cylinders and cones.
DirectX Tutorial 9: Textured Spheres, Cylinders and Cones
In this tutorial we will look at how to create a textured sphere, cylinder and cone. We'll set-up the normals so that the objects are shaded correctly. Also, we'll derive all of our classes from one base class called CBase, which has methods for HTML logging and Normal calculations. Our example application for this tutorial will consist of two cylinders, two cones and a simple model of the Moon rotating around Earth, which is rotating around the Sun. You can download the full source code by clicking the "Download Source" link above.
Our cylinder will be made up from two triangle fans, one for the top and one for the bottom. The sides of the cylinder will be made up from a triangle strip. Fig 9.1 below shows a diagram of the vertices and polygons for a cylinder. It also includes the normals for the four corners of one segment. To create our cylinder we have not used an index buffer, only a vertex buffer. This is because; although we do have shared vertices (between the sides and the top/bottom faces) we want different normals so the edges around the top and bottom appear sharp.