Using Index Buffers

If you remember our first application rendering cubes, we created data for a total of 36 vertices. There were 2 triangles for each face of the cube; 6 faces multiplied by 2 triangles equals 12 primitives. Since each primitive has 3 vertices, we get our 36. However, in reality, there were only 8 different vertices being used; one for each corner of the cube.

Storing the same vertex data multiple times in a small application such as this may not seem like that big of a deal, but in a large-scale application where you're storing mass amounts of vertex data, saving room by not duplicating your vertices is nothing but a good thing. Luckily, Direct3D has a mechanism for sharing vertex data inside a primitive called index buffers.

Like its name implies, an index buffer is a buffer that stores indices into vertex data. The indices stored in this buffer can be 32-bit (integer data) or 16-bit (short data). Unless you really need more indices than provided by the short data type, stick with that as it is half the size of the integer data.

When using an index buffer to render your primitives, each index in your index buffer corresponds directly to a vertex stored in your vertex data. For example, if you were rendering a single triangle with the indices of 0, 1, 6, it would render the triangle using the vertices mapped to those indices. Let's modify our cube drawing application to use indices. First, modify our vertex data creation function as shown in Listing 4.3:

Listing 4.3 Creating Vertices for Our Cube
 vb = new VertexBuffer(typeof(CustomVertex.PositionColored), 8, device, Usage.Dynamic |     Usage.WriteOnly, CustomVertex.PositionColored.Format, Pool.Default); CustomVertex.PositionColored[] verts = new CustomVertex.PositionColored[8]; // Vertices verts[0] = new CustomVertex.PositionColored(-1.0f, 1.0f, 1.0f, Color.Purple.ToArgb()); verts[1] = new CustomVertex.PositionColored(-1.0f, -1.0f, 1.0f, Color.Red.ToArgb()); verts[2] = new CustomVertex.PositionColored(1.0f, 1.0f, 1.0f, Color.Blue.ToArgb()); verts[3] = new CustomVertex.PositionColored(1.0f, -1.0f, 1.0f, Color.Yellow.ToArgb()); verts[4] = new CustomVertex.PositionColored(-1.0f, 1.0f, -1.0f, Color.Gold.ToArgb()); verts[5] = new CustomVertex.PositionColored(1.0f, 1.0f, -1.0f, Color.Green.ToArgb()); verts[6] = new CustomVertex.PositionColored(-1.0f, -1.0f, -1.0f, Color.Black.ToArgb()); verts[7] = new CustomVertex.PositionColored(1.0f,-1.0f,-1.0f, Color.WhiteSmoke.ToArgb()); buffer.SetData(verts, 0, LockFlags.None); 

As you can see, we dramatically lowered the amount of vertex data, by only storing the 8 vertices that make up the corners of the cube. Now, we still want to draw 36 vertices, just with different orders of these 8 vertices. Since we know what our vertices are now, what are the 36 indices to these vertices that we would need to use to draw our cube? Looking at our previous application, we could compare each of the 36 vertices used, and find the appropriate index in our new list. Add our list of indices, shown in Listing 4.4, to the declaration section:

Listing 4.4 The Box Index Buffer Data
 private static readonly short[] indices = {     0,1,2, // Front Face     1,3,2, // Front Face     4,5,6, // Back Face     6,5,7, // Back Face     0,5,4, // Top Face     0,2,5, // Top Face     1,6,7, // Bottom Face     1,7,3, // Bottom Face     0,6,1, // Left Face     4,6,0, // Left Face     2,3,7, // Right Face     5,2,7 // Right Face }; 

The index list is broken down to three for each triangle we will be drawing, for ease of reading. As you can see, the front face is created by using two triangles. The first triangle uses vertices 0, 1, and 2, while the second triangle uses vertices 1, 3, and 2. Similarly, for the right face, the first triangle uses vertices 2, 3, and 7, while the second triangle uses 5, 2, and 7. All of the back face culling winding rules will still apply when using indices as well.

However, just having the list of indices doesn't really help us until we update our application to use them. For this, you will need to create an index buffer. Add the following line after the declaration for our vertex buffer:

 private IndexBuffer ib = null; 

This object will be used to store our indices and give Direct3D access to them. This object is remarkably similar to the vertex buffer object we've already created; it just will store indices instead. Let's instantiate this object and fill it with data. After the instantiation of the vertex buffer, add the code in Listing 4.5:

Listing 4.5 Creating the Index Buffer
 ib = new IndexBuffer(typeof(short), indices.Length,device,Usage.WriteOnly,Pool.Default); ib.Created += new EventHandler(this.OnIndexBufferCreate); OnIndexBufferCreate(ib, null); private void OnIndexBufferCreate(object sender, EventArgs e) {     IndexBuffer buffer = (IndexBuffer)sender;     buffer.SetData(indices, 0, LockFlags.None); } 

You'll notice that the constructor for the index buffer mirrors that of the vertex buffer. The only difference is the restrictions of the type parameter. As mentioned earlier, you can only use the short (System.Int16) or integer (System.Int32) data types for the index data. We also hook the created event and call our hook function for the first run. We then simply fill our index buffer with the data we needed.

Now, we just need to fix our rendering code to actually use this data. If you remember, there was a function called "SetStreamSource" to tell Direct3D which vertex buffer to use when rendering. Of course, there's a similar item for index buffers, however this time it's simply a property since there can only be one type of index buffer in use at a time. Immediately after our SetStreamSource call, set this property:

 device.Indices = ib; 

Now that Direct3D knows about our index buffer, we need to modify our drawing calls. Currently, our drawing calls are trying to draw 12 primitives (36 vertices) from our vertex buffer, which will naturally fail since it only holds 8 vertices. Add the DrawBox function back:

 private void DrawBox(float yaw, float pitch, float roll, float x, float y, float z) {     angle += 0.01f;     device.Transform.World = Matrix.RotationYawPitchRoll(yaw, pitch, roll) *         Matrix.Translation(x, y, z);     device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0,         indices.Length / 3); } 

We've changed our drawing call from DrawPrimitives to DrawIndexedPrimitives. Let's look at the prototype for this function:

 public void DrawIndexedPrimitives ( Microsoft.DirectX.Direct3D.PrimitiveType primitiveType      , System.Int32 baseVertex , System.Int32 minVertexIndex , System.Int32 numVertices ,     System.Int32 startIndex , System.Int32 primCount ) 

The first parameter is the same as the previous function, the type of primitives we will be drawing. The baseVertex parameter is the offset from the start of the index buffer to the first vertex index. The minVertexIndex is the minimum vertex index for vertices in this call. The numVertices member is relatively self-explanatory (the number of vertices used during this call); however, it is starting from the baseVertex + minVertexIndex parameters. The startIndex member refers to the location in the array to start reading vertices. The last parameter stays the same (the number of primitives to draw).

So, as you can see, we are simply drawing our 8 vertices, using our index buffer to render 12 primitives for our cube. Now let's remove our DrawPrimitives calls and replace them with calls into our DrawBox method, shown in Listing 4.6:

Listing 4.6 Drawing Boxes
 // Draw our boxes DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f, angle / (float)Math.PI /     4.0f, 0.0f, 0.0f, 0.0f); DrawBox(angle / (float)Math.PI, angle / (float)Math.PI / 2.0f, angle / (float)Math.PI *     4.0f, 5.0f, 0.0f, 0.0f); DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 4.0f, angle / (float)Math.PI /     2.0f, -5.0f, 0.0f, 0.0f); DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f, angle / (float)Math.PI /     4.0f, 0.0f, -5.0f, 0.0f); DrawBox(angle / (float)Math.PI, angle / (float)Math.PI / 2.0f, angle / (float)Math.PI *     4.0f, 5.0f, -5.0f, 0.0f); DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 4.0f, angle / (float)Math.PI /     2.0f, -5.0f, -5.0f, 0.0f); DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 2.0f, angle / (float)Math.PI /     4.0f, 0.0f, 5.0f, 0.0f); DrawBox(angle / (float)Math.PI, angle / (float)Math.PI / 2.0f, angle / (float)Math.PI *     4.0f, 5.0f, 5.0f, 0.0f); DrawBox(angle / (float)Math.PI, angle / (float)Math.PI * 4.0f, angle / (float)Math.PI /     2.0f, -5.0f, 5.0f, 0.0f); 

Running this application now will render very colorful cubes spinning around your scene.

The reason each vertex in our list has a different color is to visually show one of the "drawbacks" to using index buffers to share vertices. When the vertices are shared across multiple primitives, all of the vertex data is shared, including colors and normal data. When determining whether you can really share these vertices, you must first determine if sharing this data can cause lighting or color errors (since lighting calculations are based on the normal data). You can see that each corner of the cube is rendered in the color for its vertex, and the faces of the cube are interpolated from the colors of its vertices.



Managed DirectX 9 Graphics and Game Programming, Kick Start
Managed DirectX 9 Kick Start: Graphics and Game Programming
ISBN: B003D7JUW6
EAN: N/A
Year: 2002
Pages: 180
Authors: Tom Miller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net