//Turn on lighting
m_pD3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
//Set ambient light level
m_pD3DDevice->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(32, 32, 32));
Step 3: Setup Material
To setup the materials for our cubes we need to add a new method to CCuboid called SetMaterial which will enable use to have a different material for each cube if we wish. We define a member variable of CCuboid called m_matMaterial which is a structure of type D3DMATERIAL8. We then set values for each attribute of the structure. Set need to define the Diffuse, Ambient and Specular reflection RGBA values followed by the Emissive RGBA value. Make sure that you initialise all of these value (especially Emissive) otherwise you may find that your lighting does not work correctly.
bool CCuboid::SetMaterial(D3DCOLORVALUE rgbaDiffuse, D3DCOLORVALUE rgbaAmbient, D3DCOLORVALUE rgbaSpecular, D3DCOLORVALUE rgbaEmissive, float rPower) {
//Set the RGBA for diffuse light reflected from this material.
m_matMaterial.Diffuse = rgbaDiffuse;
//Set the RGBA for ambient light reflected from this material.
m_matMaterial.Ambient = rgbaAmbient;
//Set the color and sharpness of specular highlights for the material.
m_matMaterial.Specular = rgbaSpecular;
m_matMaterial.Power = rPower;
//Set the RGBA for light emitted from this material.
m_matMaterial.Emissive = rgbaEmissive;
return true;
}
In the CCuboid constructor, we call SetMaterial to give the cube a default material, as shown below.
//Set material default values (R, G, B, A)
D3DCOLORVALUE rgbaDiffuse = {1.0, 1.0, 1.0, 0.0,};
D3DCOLORVALUE rgbaAmbient = {1.0, 1.0, 1.0, 0.0,};
D3DCOLORVALUE rgbaSpecular = {0.0, 0.0, 0.0, 0.0,};
D3DCOLORVALUE rgbaEmissive = {0.0, 0.0, 0.0, 0.0,};
SetMaterial(rgbaDiffuse, rgbaAmbient, rgbaSpecular, rgbaEmissive, 0);
Finally, in our CCuboid's Render method, we need to use the SetMaterial method to tell DirectX that we want to use our material for all future vertices.
m _pD3DDevice->SetMaterial(&m_matMaterial);
Step 4: Generate Normals
We've changed the way that the cube is made up. Rather than three triangle strips, we are now using a triangle list of 12 triangles which is 36 vertices! So we define our vertices as before, except this time we remove the colour component and replace it with a Normal vector initialised to zero. We then loop around each triangle and calculate what the Normal vector should be for that triangle using the GetTriangeNormal method. We will then set each of the three vertices normals for that triangle to be the same as the triangle polygon Normal itself. The two code snippets below show the GetTriangeNormal method and the triangle looping.
We set the vertices to be that same as the polygon because we are rendering a shape with sharp edges. We only really need to average the normals of shared vertices if we are drawing a smooth shape like a sphere.
D3DVECTOR CCuboid::GetTriangeNormal(D3DXVECTOR3* vVertex1, D3DXVECTOR3* vVertex2, D3DXVECTOR3* vVertex3) {
D3DXVECTOR3 vNormal;
D3DXVECTOR3 v1;
D3DXVECTOR3 v2;
D3DXVec3Subtract(&v1, vVertex2, vVertex1);
D3DXVec3Subtract(&v2, vVertex3, vVertex1);
D3DXVec3Cross(&vNormal, &v1, &v2);
D3DXVec3Normalize(&vNormal, &vNormal);
return vNormal;
}
//Set all vertex normals
int i;
for (i = 0; i < 36; i += 3) {
vNormal = GetTriangeNormal(&D3DXVECTOR3(cvVertices[i].x, cvVertices[i].y, cvVertices[i].z), &D3DXVECTOR3(cvVertices[i + 1].x, cvVertices[i + 1].y, cvVertices[i + 1].z), &D3DXVECTOR3(cvVertices[i + 2].x, cvVertices[i + 2].y, cvVertices[i + 2].z));
cvVertices[i].nx = vNormal.x;
cvVertices[i].ny = vNormal.y;
cvVertices[i].nz = vNormal.z;
cvVertices[i + 1].nx = vNormal.x;
cvVertices[i + 1].ny = vNormal.y;
cvVertices[i + 1].nz = vNormal.z;
cvVertices[i + 2].nx = vNormal.x;
cvVertices[i + 2].ny = vNormal.y;
cvVertices[i + 2].nz = vNormal.z;
}
Fig 7.6 below shows the Normals for three of the faces of the cube. The Normals for each of the other vertices will also be the same as the faces. If we were to average the Normals, the cube would appear to have rounded edges. We should only average the Normals of shared vertices if we are rendering a smooth object. I'll show you how to average Normals into one in a future tutorial.
Fig 7.6
Finally, in our CCuboid's Render method, we need to change how we render our cube by using D3DPT_TRIANGLELIST rather than D3DPT_TRIANGLESTRIP.
m_pD3DDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 12);
Once you have made these changes, you should finish up with four rotating cubes, each with a different texture (shown below). There is a light source in the center of the four cubes, so as they rotate, you can see that some faces are lit and some are in shadow.
In this tutorial we've covered a lot of stuff. We've learnt all about the different types of lights and their properties. We've seen how materials can affect how an objects looks once rendered. In the next tutorial we'll take a look at Index Buffers.
DirectX Tutorial 8: Index Buffers
In this tutorial we will look at Index Buffers. First of all, we'll improve our CCuboid class to use index buffers. Then we'll create a new class called CTerrain, which we can use to generate a very simple terrain. Once the terrain has been created, we'll add a grass texture to make it seem more realistic. You can download the full source code by clicking the "Download Source" link above.
An index buffer is a memory buffer that holds indices that "point" to vertices in your vertex buffer. When a scene is rendered, DirectX performs certain calculations on each vertex such as lighting and transformations. What we want to do is minimise the amount of calculations that DirectX has to do, therefore, we need to minimise the number of vertices. We can do this using an index buffer. Lets say you want to draw a square. Your square will be made up from two triangles, which is six vertices (using a triangle list). But we only really need four vertices to define a square (one for each corner). We had to use six vertices because two of them are shared (have the same value). Because we have shared vertices, it's a good idea to use an index buffer. Here's how it works: we define the four corners of our square as vertices in our vertex buffer. Then we define six indices in our index buffer each of which "point" to a vertex in the vertex buffer. We then render our triangles from the indices in the index buffer and so, only use four vertices. Fig 8.1 below shows this example.
Fig 8.1
In the example above, we have only used four vertices to describe our square, which is a saving of two vertices. The index buffer contains six elements, each of these elements contains an index to a vertex in the vertex buffer. Notice that the order in the index buffer is in a clockwise direction, this is the order in which vertices will be rendered. We then render our scene as before, except this time we use the index buffer in addition to our vertex buffer. So, if we apply this technique to our cube, we will be saving twelve vertices (24 rather than 36). That doesn't sound that much does it? But imagine a scene that contains 100 cubes, that's a saving of 1200 vertex calculations per frame!