Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
69 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Reconfiguring our
 Direct3D application

 Basic 3D Geometry
 Vertex Buffers and
 Index Buffers

 Rendering Summary

 Source code
 Printable version
 Discuss this article

The Series
 Part 1
 Part 2
 Part 3

Vertex Buffers and Index Buffers

Well, that was one rather large 7 A4 page section done with, things should fly by a little quicker now that the basic theory has been covered. As with most things, once you’ve jumped over the initial couple of hurdles (setting up D3D, basic 3D geometry) it’s all easy - well, sort of J

This section is dealing with enhancing your capabilities for rendering geometry, making things easier and faster. Which I’m sure you’ll appreciate…

The first stop will be vertex buffers - what are these? I hear you asking… A vertex buffer is an area of memory where you can store your vertex data, this has the advantage of being easier to render - both for you and for the driver (it can perform various optimisations, and access the data faster) and allowing you to archive certain pieces of geometry, a generic cube for example… Whilst not entirely true it can also stop most accidental overwriting/corrupting errors - once the buffer is created it’s not something you can accidentally change without realising it - whereas an array of vertex structures as a standard variable could easily be overwritten by a rogue function or library (however unlikely you think it, it will happen sometime).

Creating a vertex buffer is extremely simple once you have the basics from the last section under your belt. At the simplest level it involves copying the data you generated to a custom buffer…

The first part is allocating some space for our buffer:

'In Declarations section
Dim vbCube As Direct3DVertexBuffer8

'at the end of InitialiseGeometry() sub
Set vbCube = D3DDevice.CreateVertexBuffer(Len(CubeVerts(0)) * 36, 0, FVF_LVERTEX, D3DPOOL_MANAGED)
If vbCube Is Nothing Then Debug.Print "ERROR: Could not create vertex buffer": Exit Sub

Not too complicated really, first we create the buffer with the function built into the Direct3DDevice8 class, then we check to make sure it was created successfully - if it wasn’t it usually generates an error on that line. The parameters for the CreateVertexBuffer() function look like:

CreateVertexBuffer( _
    LengthInBytes As Long, _
    Usage As Long, _
    FVF As Long, _
    Pool As CONST_D3DPOOL) As Direct3DVertexBuffer8

LengthInBytes: This is the amount of memory that will be set aside for your buffer, it must be at least as big as the data you want to store in it - any less and you’ll get an error when copying the data in, any more and you’ll be wasting space. This value can be computed using the Len() function to find the size of one vertex structure, then multiplying it by the number of structures that your using.

Usage: This can be left as 0 for most uses, but more specialised cases require that you let Direct3D know that your going to be doing different things with this vertex buffer. For example, specifying D3DUSAGE_DYNAMIC will tell Direct3D to let the driver know that we’re probably going to be changing the contents around quite a lot - it will then choose the most optimal place in memory to put it. If you don’t specify this, the buffer will be considered static - and the driver will place it in the most optimal memory location for that use - which will mean that any lock-write-unlock operations are slower (it assumed that you weren’t likely to be doing any).

FVF: This lets Direct3D know what sort of vertices you will be storing here, should you ask D3D to perform any operations on this buffer later on it will base them on this flag. Set this to be the same as the one you use for rendering.

Pool: This allows you to tell D3D where you would like the vertex buffer to be stored, note that it’s where you would LIKE it to be - The driver may well override this setting - should it be impractical (lack of space/lack for support for example). The 3 options are: D3DPOOL_DEFAULT - it’s left mostly up to the driver to choose where it goes - usually in video memory, but other places if the usages flags suggest otherwise. D3DPOOL_MANAGED - D3D will store a master copy in system memory and will copy the buffer to video memory as and when its required. D3DPOOL_SYSTEMMEM - the buffer will be stored in system memory, which isn’t usually accessable by the 3D device, and it will tend to be quite slow.

Now that we’ve created our vertex buffer we want to put some stuff in it! For this example we’ll copy our cube vertices into the buffer and then render it from there. For this we use a little helper function to copy the data:

D3DVertexBuffer8SetData vbCube, 0, Len(CubeVerts(0)) * 36, 0, CubeVerts(0)

That call will fill the specified vertex buffer with the specified amount of information from the array provided. A quick run through of the parameters and what they mean then:

D3DVertexBuffer8SetData( _
    VBuffer As Direct3DVertexBuffer8, _
    Offset As Long, _
    Size As Long, _
    Flags As Long, _
    Data As Any) As Long

Vbuffer: obviously this is the vertex buffer object that you want the data put into.

Offset: The offset in bytes from the beginning of the buffer that we start placing new data - useful if your only wanting to change vertices 10-19 (for example).

Size: The amount of data that’s going to be copied in, also the amount that D3D expects to find in the data array. This is the same calculation as used in creating the buffer. Try to make sure beforehand that this doesn’t exceed the data source size, or the remaining/total space in the vertex buffer

Flags: A set of flags defining how you want the data to be replaced/copied - look up CONST_D3DLOCKFLAGS in the object browser for more details.

Data: This is the source array, it can be of any data type - whilst you could trick D3D into storing any type of data it’s supposed to be vertex data only (make sure you don’t accidentally include any state variables/non-vertex variables).

Okay, you now have all the information required to create a vertex buffer - but currently you cant actually do anything with it. For this sample all we’ll do is render it - there are a few other things you can do, but I’ll let you explore those yourself…

'replace:
D3DDevice.DrawPrimitiveUP D3DPT_TRIANGLELIST, 12, CubeVerts(0), Len(CubeVerts(0))

'With:
D3DDevice.SetStreamSource 0, vbCube, Len(CubeVerts(0))
D3DDevice.DrawPrimitive D3DPT_TRIANGLELIST, 0, 12

As you can see, rendering gets considerably easier when using vertex buffers - tell it where the vertex buffer is, and then what part you want rendering. As a side note, the stream number (The first parameter in SetStreamSource) should be left as 0 in most cases - things such as vertex shaders manipulate these further.

Now that you have vertex buffers under your belt we can take a look at index buffers, these require vertex buffers (not technically true, but I’ll explain later), which is why they came last. In fact, this is the last section in this article - we are definitely homeward bound…

If you look at our cube geometry you’ll notice that it’s extremely inefficient - we use 36 vertices to describe an object that (in real life) only has 8 vertices. Wouldn’t it be extremely useful if we could only place 1 vertex in each corner, rather than have 3-6 vertices sharing the same coordinate, whilst not always the case, in our example they have the same colour as well - several vertices that are all identical.

This is where indices and index buffers come into play - they allow us to use vertices more than once (to put it simply). We can create 8 vertices, and then, using an index list say that triangle 1 uses vertices 1,2 and 3 and triangle 2 uses vertices 2,4 and 3 - reusing vertices. This has the advantage of being relatively fast, and also saves on the amount of data involved - the index list will always be smaller than the vertex list, and therefore the amount of data that’s processed and copied also decreases.

Unfortunately they aren’t quite the miracle invention I’m making them out to be - two topics that we haven’t yet discussed: Lighting and textures can become significantly worse/harder when using indices - it’s not always the case, but in cases such as the cube example it would be very annoying. The exact reasoning will be discussed in the relevant sections later on…

To set up and use indices/index buffers we first need to create a vertex buffer with the key vertices in it - the following code does this for the cube sample:

'In the declarations section:
Dim vbCubeIdx As Direct3DVertexBuffer8
Dim ibCube As Direct3DIndexBuffer8
Dim vList(0 To 7) As LITVERTEX 'the 8 vertices required
Dim iList(0 To 35) As Integer 'the 36 indices required (note that the number is the
                              'same as the vertex count in the previous version).

'in the create geometry section:
vList(0) = CreateLitVertex(-1, -1, -1, &HFFFFFF, 0, 0, 0)
vList(1) = CreateLitVertex(-1, -1, 1, &HFF0000, 0, 0, 0)
vList(2) = CreateLitVertex(-1, 1, -1, &HFF00, 0, 0, 0)
vList(3) = CreateLitVertex(-1, 1, 1, &HFF, 0, 0, 0)
vList(4) = CreateLitVertex(1, -1, -1, &HFF00FF, 0, 0, 0)
vList(5) = CreateLitVertex(1, -1, 1, &HFFFF00, 0, 0, 0)
vList(6) = CreateLitVertex(1, 1, -1, &HFFFF, 0, 0, 0)
vList(7) = CreateLitVertex(1, 1, 1, &HFF8000, 0, 0, 0)

Set vbCubeIdx = D3DDevice.CreateVertexBuffer(Len(vList(0)) * 8, 0, FVF_LVERTEX, D3DPOOL_MANAGED)
If vbCubeIdx Is Nothing Then Debug.Print "ERROR: Could not create vbCubeIdx": Exit Sub

D3DVertexBuffer8SetData vbCubeIdx, 0, Len(vList(0)) * 8, 0, vList(0)

I’m not going to cover this part again - reread the previous section if you’re still unclear as to setting up a vertex buffer.

The next part is the new part, we must set up an index list. All this requires is that we fill an array of 16 bit integers (you can use 32 bit integers if you need to) and then copy them to the new array. We’ll be rendering our cube using a triangle list again - so we’re going to need to describe 12 triangles again, because of this we can pretty much copy exactly what we did for the original triangle. This is what it’s going to look like:

'top
iList(0) = 2: iList(1) = 6: iList(2) = 3
iList(3) = 6: iList(4) = 7: iList(5) = 3

'bottom
iList(6) = 0: iList(7) = 4: iList(8) = 1
iList(9) = 4: iList(10) = 5: iList(11) = 1

'left
iList(12) = 2: iList(13) = 3: iList(14) = 0
iList(15) = 3: iList(16) = 1: iList(17) = 0

'right
iList(18) = 6: iList(19) = 7: iList(20) = 4
iList(21) = 7: iList(22) = 5: iList(23) = 4

'front
iList(24) = 3: iList(25) = 7: iList(26) = 1
iList(27) = 7: iList(28) = 5: iList(29) = 1

'back
iList(30) = 2: iList(31) = 6: iList(32) = 0
iList(33) = 6: iList(34) = 4: iList(35) = 0

I’ve grouped them together into their triangles - each line is a triangle, and there are two triangles to each face - so two lines under each comment/heading.

Hopefully it’s all fairly explanatory; if we take the first triangle as an example, it uses the vertices 2,6 and 3 to be rendered, these are in the standard clockwise order.

The next part we need to cover is creating an index buffer, and then copying the data into it. Luckily this is almost identical to the creation of a vertex buffer - many of the parameters are the same. To copy our index list into an index buffer we use this piece of code:

Set ibCube = D3DDevice.CreateIndexBuffer(Len(iList(0)) * 36, 0, D3DFMT_INDEX16, D3DPOOL_MANAGED)
If ibCube Is Nothing Then Debug.Print "ERROR: Could not create the index buffer": Exit Sub

D3DIndexBuffer8SetData ibCube, 0, Len(iList(0)) * 36, 0, iList(0)

Straight away you can see the similarities; the only major different when creating the index buffer is that we need to specify what format the indices are going to be in. There are only two valid options here (despite it listing 30-40 formats): D3DFMT_INDEX16 and D3DFMT_INDEX32, these refer to the actual data type that we use to store our indices, a 16 bit integer (The integer data type) can only hold up to 32,767 indices (the maximum + value for an integer), and a 32 bit integer (the long data type) can only hold up to 2,147,483,647 (the maximum + value for a long). Obviously using 32 bit indices take up double the amount of space (4 bytes per value, rather than 2 bytes per value in 16 bit), so unless you are using an extremely large number of indices you should try and stick to using 16 bit indices.

Now that we have our index data stored in a buffer we need to be able to do something with it, for this example all we’re going to do is render it - there is very little else you can do with them anyway. The following code segment will render our cube using our vertex buffer/index buffer combo:

D3DDevice.SetStreamSource 0, vbCubeIdx, Len(vList(0))
D3DDevice.SetIndices ibCube, 0
D3DDevice.DrawIndexedPrimitive D3DPT_TRIANGLELIST, 0, 36, 0, 12

All it involves is setting the current index buffer and vertex buffer - make sure they match up though, then using DrawIndexedPrimative() to render from them. The parameters are fairly straight forward for the rendering call, you can use the third and fourth to set a range that you want rendering (12-24 for example), you can use the second parameter to make an offset in the vertex buffer (all values in the index list get this number added to them, if it were 10, and iList(3) was 12 the actual vertex used would be 22). The last value states how many primitive we’re rendering - in this case the whole cube, 12 triangles.

There are two final things to be covered about the usage of indices and buffers; the first one is about rendering without using the buffers. It is perfectly possible to render our indexed cube without storing the index/vertex data in a buffer, but it is often a great deal slower. The call for doing this is quite a lengthy one - you need to specify the details of both the vertex and index buffers:

D3DDevice.DrawIndexedPrimitiveUP D3DPT_TRIANGLELIST, 0, 8, 12, _
        iList(0), D3DFMT_INDEX16, vList(0), Len(vList(0))

A very quick summary of what goes where:

PrimitiveType: What type of primitives we’re rendering

MinVertexIndex: The Starting vertex to render from

NumVertexIndices: The number of vertices that we’re going to render.
PrimitiveCount: The number of triangles that we are rendering

IndexDataArray: The first element in the array storing indices

IndexDataFormat: What format the index data is in, 16 or 32 bit

VertexStreamZeroDataArray: The first element of the vertex data array

VertexStreamZeroStride: The size of one vertex element

If you have the SDK help file you may realise that the 2nd and 3rd parameters have different names - This is a typing error in the help file, the parameters I’ve listed above come straight from the data-tip that appears when you type the line into VB.





Next : Rendering Summary