Using
Index Buffers to store 3D Geometry
Author
:
Jack Hoxley
Written : 27th December 2000
Contact : [Web]
[Email]
Download : Graph_09.Zip
[12 Kb]
Contents
of this lesson:
1. Introduction
2. Generating our geometry
3. Rendering our geometry
1.
Introduction
Welcome
back to the second of the advanced geometry tutorials, things should be starting
to fall into place now - and you should be more than capable of creating a very
basic 3D engine with your current knowledge. However, there are still a few
things to learn before you can create a reasonable, modern 3D engine for your
game.
This
lesson is going to discuss one trick that you may well find extremely useful
if you go onto develop your own engine. Indices - these are a series of numbers,
stored in an index buffer, that reference vertices in a vertex buffer. They
may not seem very useful, but you can reduce your vertex count 10 fold using
them. Consider the cube that I've shown you in several previous lessons. With
your current knowledge you could create it using 36 vertices, 18 if you're clever;
or you could just generate it using a 3D modeller and import it in as an .X
file. The first way is very inefficient, you're using up a large number of vertices
for a very very simple piece of geometry. The second way is okay, but can be
a bit of a pain when it comes to changing the colours and textures. Now consider
the use of an index buffer...
An
index buffer is basically a series of numbers (of integer datatype); these numbers
reference (any number of times) vertices stored in a vertex buffer. For example,
we could generate a vertex buffer in with 8 vertices defining a cube, we could
then create an index buffer with 36 entries in it (same number of vertices as
used before) to reuse those vertices. Think of it like this - the indices for
triangle 1 could be 0, 1 and 3 - which, to the renderer, says: "Make a
triangle using vertices 0, 1 and 3". So, all of a sudden our cube becomes
8 vertices instead of the original 36 vertices.
Another
way of thinking about it is to realise that of those 36 vertices we've previously
used anything from 3-6 vertices have shared the same world-space position, and
the same colour; so why not represent those 3-6 vertices with only 1...
One
of the main uses I've found for indices in the past is when making custom file
formats. You can store the data in a file for 3500 vertices and end up with
a rather large data file, or you could store 1200 vertices and a list of 3500
indices and get a relatively small file.
Despite
the fact that indices and index buffers are extremely useful, there are also
several downsides to using them, the main one being that all indices sharing
the same vertex must have the same properties - same position, same colour,
same texture coordinates, same normal.
In our cube example we couldn't redesign it so that one face was blue,
one face was green and so on.... each face would blend into each other
Texturing becomes really difficult. How would you set texture coordinates
for our cube so that you got different parts of a texture on each face - or
a different texture on each face. There are some tricks involving mult-pass
texturing and multiple stage texturing, but their not always available and are
often quite slow.
Lighting normals must be the same, this isn't too difficult as you average
the normals for the 3 sides of our cube using addition (as illustrated in the
lighting tutorial). Because all the normals will be averaged it sometimes looks
a little odd - not always, but sometimes the lights appear to behave incorrectly
and unnaturally - something you want to avoid when making realistic / semi-realistic
environments.
The
arguments for and against have been laid out, it's up to you to decide if and
when you want to use index buffers. Either way, it's useful to know how they
work...
2.
Generating our Geometry
Thankfully
this part actually gets much easier when using index buffers; Hopefully you've
been following the series from the beginning, and you should remember the several
occasions that I've made a cube in the sample application - and in the "InitialiseGeometry(
)" procedure we have 150 lines of repetitive code for generating a simple
cube. You may not have realised it, but it was an absolute pain to search for
an incorrect vertex in there - and writing the code from scratch was annoying
enough. Using indices we create 8 vertices and 36 indices, if the vertex positions
are wrong I have 8 of them to look through - not too hard, and if the indices
are wrong I just have to juggle a few numbers around.
Private
Function
InitialiseGeometry()
As
Boolean
On
Error
GoTo
BailOut: '//Setup our Error handler
'//1. Generate the 8 vertices for our cube (2x2x2 in size)
CubeVertices(0)
=
CreateLitVertex(-1,
-1,
-1,
&HFF0000,
0, 0,
0)
CubeVertices(1)
=
CreateLitVertex(-1,
1, -1,
&HFF00&,
0, 0,
0)
CubeVertices(2)
=
CreateLitVertex(1,
-1,
-1,
&HFF&,
0, 0,
0)
CubeVertices(3)
=
CreateLitVertex(1,
1, -1,
&HFF00FF,
0, 0,
0)
CubeVertices(4)
=
CreateLitVertex(-1,
-1, 1,
&HFFFF00,
0, 0,
0)
CubeVertices(5)
=
CreateLitVertex(-1,
1, 1,
&HFFFF,
0, 0,
0)
CubeVertices(6)
=
CreateLitVertex(1,
-1, 1,
&HFFCC00,
0, 0,
0)
CubeVertices(7)
=
CreateLitVertex(1,
1, 1,
&HFFFFFF,
0, 0,
0)
'//2. Create the vertex buffer
Set
VBuffer
=
D3DDevice.CreateVertexBuffer(Len(CubeVertices(0))
* 8,
0,
Lit_FVF,
D3DPOOL_DEFAULT)
If
VBuffer
Is
Nothing
Then
GoTo
BailOut:
D3DVertexBuffer8SetData
VBuffer,
0,
Len(CubeVertices(0))
* 8,
0,
CubeVertices(0)
'//3. Generate our indices
'front
CubeIndices(0)
= 0:
CubeIndices(1)
= 1:
CubeIndices(2)
= 2
CubeIndices(3)
= 1:
CubeIndices(4)
= 3:
CubeIndices(5)
= 2
'Right
CubeIndices(6)
= 2:
CubeIndices(7)
= 3:
CubeIndices(8)
= 6
CubeIndices(9)
= 3:
CubeIndices(10)
= 7:
CubeIndices(11)
= 6
'Back
CubeIndices(12)
= 6:
CubeIndices(13)
= 7:
CubeIndices(14)
= 4
CubeIndices(15)
= 7:
CubeIndices(16)
= 5:
CubeIndices(17)
= 4
'Left
CubeIndices(18)
= 4:
CubeIndices(19)
= 5:
CubeIndices(20)
= 0
CubeIndices(21)
= 5:
CubeIndices(22)
= 1:
CubeIndices(23)
= 0
'Top
CubeIndices(24)
= 1:
CubeIndices(25)
= 5:
CubeIndices(26)
= 3
CubeIndices(27)
= 5:
CubeIndices(28)
= 7:
CubeIndices(29)
= 3
'Bottom
CubeIndices(30)
= 2:
CubeIndices(31)
= 6:
CubeIndices(32)
= 0
CubeIndices(33)
= 6:
CubeIndices(34)
= 4:
CubeIndices(35)
= 0
'//4. Create our index buffer
Set
IBuffer
=
D3DDevice.CreateIndexBuffer(Len(CubeIndices(0))
* 36,
0,
D3DFMT_INDEX16,
D3DPOOL_DEFAULT)
D3DIndexBuffer8SetData
IBuffer,
0,
Len(CubeIndices(0))
* 36,
0,
CubeIndices(0)
InitialiseGeometry
= True
Exit
Function
BailOut:
Debug.Print
"Error
occured
in
InitGeometry()
Code"
InitialiseGeometry
=
False
End
Function |
|
|
|
There,
easy isn't it. I've written all the indices so that there is 1 triangle per
line - 2 triangles per face with a heading. The indices still need to be in
the correct order - clockwise usually, and you cant put invalid vertex numbers
in- so you still have to be a little bit careful.
The
first stage, whilst it doesn't have to be, is creating the vertices and vertex
buffers - we've covered them in previous lessons and aren't that difficult so
I'll skip straight along to indices and index buffers.
The
first part of generating indices is to fill the correct sized array with the
correct values - the array is of type integer and has the same number of elements
that we'd normally use when creating vertices - for a cube this would be 36.
The
second part creates a blank area in memory for holding our array of indices;
this must be done before we try and fill the buffer with our data - otherwise
some strange memory errors are going to appear. To create an index buffer we
use the D3DDevice.CreateIndexBuffer( ) call, which takes the following parameters:
LengthInBytes
As Long = How much memory we need to allocate, this is basically "Len(
first element in array ) * number of elements in the array"
Usage As Long = This allows the device to place our index buffer in the
most efficient place available; see the "D3DUSAGE_" flags in the object
browser. Leave as 0 for most applications
Format As D3DFORMAT = What format the indices are, D3DFMT_INDEX16 and
D3DFMT_INDEX32 are the only valid flags; use D3DFMT_INDEX16 if you're using
an integer array, and D3DFMT_INDEX32 if you're using a long array - but it's
unlikely you'll ever need to go above 32767 indices anyway (even if the driver
lets you) so an integer array will almost always suffice.
Pool As CONST_D3DPOOL = Where you want the index buffer to be stored.
This can usually be left as D3DPOOL_DEFAULT, but if you intend to keep changing
the contents it's best to place it in system memory - D3DPOOL_SYSTEMMEM; D3DPOOL_MANAGED
allows DirectX and the driver to shift it's location around as it sees fit....
Finally
we fill the index buffer with our data; this is almost identical to filling
a vertex buffer with data. It uses the hidden procedure "D3DIndexBuffer8SetData",
which takes these parameters:
IBuffer
as Direct3DIndexBuffer8 = The index buffer we're using, this must be created
first - as we've just done.
Offset as Long = Where we start writing the data (in bytes); this should
be 0 if this is the first write; otherwise you can set it to other values should
you wish to insert data later on.
Size as Long = How big the Index buffer is - the same as we specified
when creating it; this shouldn't be any bigger than you created it, otherwise
it'll write to memory that doesn't belong to it and it'll give you an error,
if not crash the whole program.
Flags as Long = A combination of the CONST_D3DLOCKFLAGS enumeration;
this can be left to 0 for a first write, other values should be specified when
resetting data later on.
Data as Any = the data that we want to fill it, defined as "Any"
so that we can pass any datatype to the procedure. This will be the first element
in our array of indices that we want copying; if you wish to start elsewhere
specify a different array entry - CubeIndices(12) for example...
At
this point in time we'll have a functioning vertex buffer and index buffer -
all the information we need to render our geometry; if we really wanted we could
discard the vertex array and index array - but they may well prove to be useful
later on, it all depends on your engine/application though....
3.
Rendering our Geometry
Rendering
the geometry isn't greatly difficult, it just requires that we tell the device
where our vertex buffer is, where our index buffer is and what to render. This
is accomplished through the following code, which goes between the D3DDevice.BeginScene
and D3DDevice.EndScene calls (which I shouldn't really need to remind you about).
D3DDevice.SetStreamSource
0,
VBuffer,
Len(CubeVertices(0))
D3DDevice.SetIndices
IBuffer,
0
D3DDevice.DrawIndexedPrimitive
D3DPT_TRIANGLELIST,
0, 36,
0, 12 |
|
|
|
First
we tell it which vertex buffer we're using and what size the information is
- something you should be able to do by now. Then we tell it which index buffer
to use, and a base value - this value is most interesting if you're using different
vertex buffers but the same index buffer. This value is added to all indices
before their referenced to the vertex buffer - effectively setting it so that
you're referencing a whole new section of the vertex buffer. For example, if
this were set to 10, the first triangle 0,1,2 would actually reference 10,11,12
in the vertex buffer... (This is called the BaseVertexIndex)
Lastly
we actually render the geometry - Using a new rendering call. The parameters
go like this:
PrimitiveType As CONST_D3DPRIMITIVETYPE = How the vertices/indices are
arranged - you should be able to do this; it was described in the basic geometry
section. This should be the same as how you've designed the indices.
MinIndex As Long = This is the same as the BaseVertexIndex value in the
SetIndices call.
NumIndices As Long = The number of indices Direct3D will use, starting
from BaseVertexIndex + MinIndex
StartIndex As Long = The location in the index buffer to start reading
indices.
PrimativeCount As Long = The number of triangles being rendered.
The
above call, with the 12 and 36 numbers changed should be suitable for most uses;
but one final note - D3DPT_POINTLIST is not supported for indices and should
not be used.
There,
all you need to know about drawing index based geometry - not too complicated
really, and quite a neat trick to learn. I suggest you download the source code
for this lesson, as a lot of the missed out things are the basic framework we
have built up over the last 8 lessons.
If
you think you've understood that, or just dont care, you can move onto Lesson
10 : Advanced Geometry part 3 - Vertex / Mesh animation.
|