Thursday, April 14, 2011

Creating a basic IndexBuffer

When rendering geometry we have to sent it as primitives to the graphics card. In XNA we have to send lines or triangles, so what happens when I have to render a square? I have to draw 2 triangles, the problem here is that I'll have to store 6 vertices to render 2 triangles when I can actually use only 4 for a square, so there must be a better way to do this, to reuse the vertices I have. The index buffer addresses just this problem.

Backreferences:

The problem with sending geometry as primitives is that most of the time you have to duplicate some of the data, in a scenario like the one shown here:
Grid depincting the usefulness of indexes
Vertices shared by up to 6 triangles
As you can see we only need 24 vertices but if we send triangle by triangle we will need 96 vertices, that's 4 times more vertices. Another advantage is that if the graphics card needs to reuse a vertex that was already transformed it cand just read the already transformed one and save some processing power this way, if we have duplicate data the GPU can't make this optimization. Those of you that already know graphics programming will probably think that you can use a strip of triangles, but if you look more carefully you can't, you must change how the vertices form the surface and it get's really ugly.

Because we don't want to have duplicate data in our vertex buffer we will set up our index buffer using the following steps:
  1. Choose size of elements
  2. Create an array of those elements
  3. Create an IndexBuffer object and loding the data
 1. Choose size of elements

For the size of the indexes we have 2 options, 16 bits and 32 bits. If we use 16 bits we can use a vertex buffer of maximum 65536. Most of the time it will enough as the scene will have a lot of smaller objects or objects broken down in smaller parts. If we use 32 bits we can use a vertex buffer of over 4 billion vertices and that should be enough for now, if not (you are probably not rendering in real time) then you are forced to render the object part by part. Because we are only rendering a square I will use elements of 16 bits in size.


2. Create an array of those elements


At the moment I like to work with UInt16 and UInt32 classes for my elements, because they are unsigned so I don't have negative indexes and also exactly the right size. Now remember we are drawing a square, here it is with wireframe on:
Square rendered both solid and wireframe
Our square with a red wireframe
To get this result with the vertex buffer we already have we will create our array of indexes like so:


    UInt16[] indices = new UInt16[6];
    indices[0] = 0;
    indices[1] = 1;
    indices[2] = 2;
    indices[3] = 0;
    indices[4] = 2;
    indices[5] = 3;

As you can see we need 6 indexes from 0 to 3 (remember that the size of the elements mean how many different values we can put in the array, the array itself is only limited by memory). Also the vertices 0 and 2 (the ones that form the diagonal) are used twice (the numbers on the right represent the vertex to be drawn and the numbers on the left in what order they will be drawn).

3. Create an IndexBuffer object and loding the data

Of course now we have to load this data to the graphics memory (just like in the case of the vertices). To do this we will create a new instance of a IndexBuffer object and call it's SetData method.


    IndexBuffer indexes = new IndexBuffer(GraphicsDevice, IndexElementSize.SixteenBits, 6, BufferUsage.WriteOnly);
    indexes.SetData<UInt16>(indices);

 As you can see we have to tell the index buffer the graphics device that is going to use it to draw, an element from the enumeration called IndexElementSize so it will now just how big the elements are, the size of the array containing the indices and last how it is going to use the buffer (BufferUsage.WriteOnly means we can't read the data from the buffer back to RAM while BufferUsage.None has no restrictions). To load the data we call the SetData method, the generic can the be the type we used to create our array or an equivalent type (ushort instead of UInt16).

We now have a vertex and index buffer, almost ready to draw that square :).

No comments:

Post a Comment