Multiple vertex buffers

Jan 21, 2009 at 7:03 PM
Hi. First off, I just wanted to say thank you for this amazing API. I'm in the beginning stages of a rather large game project, and it's already making my life so much easier.

At the moment, i'm working on my terrain system, and am thinking that with the huge terrains that i'll be rendering that it might be a good idea to split things up into a few vertex buffers.
My question is what would be the best way to do this? I have gone through all of the turorials and documentation, and it seems like the VerticesGroup class may be what i'm looking for. Is this the case, or is there another way to get this done? If VerticesGroup is the way to go, would you be able to give me a quick rundown on the correct syntax that I should be using?

Any help would be greatly appreciated,

Jan 21, 2009 at 10:56 PM
Edited Jan 21, 2009 at 11:10 PM

Hello. Thanks for the comments :-)

VerticesGroup is a wrapper on using multiple vertex streams at the same time.
What this means, is that in a hypothetical situation you may wish to render using 3 separate vertex buffers, where the first stores position, the second stores texture coords, the third colours (for example). You would use a VerticesGroup for this situation - you do this when you want to reuse duplicate data.
For example, in this hypothetical case, you may have 4 things that share the same texture coordinate data and two of them share colours, but all have different positions. Without sharing data, you could create 4 buffers that each store position, tex and colours. With sharing, you'd create 4 buffers with positions, 1 with texture coords, and 2 with normals (which could save a lot of duplicate data). You'd then create 4 VerticesGroup objects to wrap them all up.

VerticesGroup does not allocate resources, it simply stores a list of other vertex buffers.
If you created a VerticesGroup object with two vertex buffers, and *both* vertex buffers had TEXCOORD0 (for example), it would use TEXCOORD0 from the first buffer, and ignore the second.

Internally, VerticesGroup is used for Instancing, using the source vertex data as the first buffer and and a dynamic buffer storing the instance matrices as the second (the matrix is stored in POSITION12-15).
The constructor simply takes an array of vertex buffers (uisng IVertices), so just pass in the buffers you want to be using, and it will sort it out for you. There are a few limits to what can be mixed, but these are all detected.

So, Terrain is an example where VerticesGroup could be used to store less data. If your terrain was broken up into (say) 32x32 vertex tiles, then you can have a single vertex buffer storing positions and texture coordinate 0 (as a flat 32x32 plane), then for each tile, you can have a vertex buffer storing a normal and a height. This is small, only 4 floats. The height could be stored in second texture coordinate, (or second position, etc). The vertex shader could then add the height to the base position (and offset the tile on the XY axis too, based on some constant). You could also use PackedVectors (HalfFloat, etc) to save even more space.
Your structure would be quite simple, something like:

struct TerrainVertex
Vector3 normal;
float tex1; //height (but stored as a texture coord)

Whereas the static terrain data could look like:

struct TerrainTile
Vector3 position;
Vector2 tex0;

You could then create your arrays, filling them with the data..

TerrainTile[] tileStaticData = new TerrainTile[32*32]; // just one

TerrainVertex[] tileData = new TerrainVertex[32*32]; //one for each tile

Creating the vertex buffers could be straight forward,

IVertices staticVB = new Vertices<TerrainTile>(tileStaticData); // just one
IIndices staticIB = new Indices<ushort>(...); //just one index buffer

for each tile:

IVertices tileVB = new Vertices<TerrainVertex>(tileData);

IVertices tileVerticesGroup = new VerticesGroup(tileStaticData, tileData); 

You could then render with the vertices group for each tile.
If you did it this way, it would reduce data size by over 50%.

My only suggestion otherwise, is don't bother with LOD. Chances are it will be slower than brute force. Unless you are drawing ~1 million triangles+ (1024x1024 terrain half visible) then lod won't help you all that much. Tiles smaller than 32x32 (2048 triangles) would also be getting inefficient (compute a bounding box per tile to cull them :-)

Good luck!

Jan 22, 2009 at 3:34 AM
That's incredibly helpful! Thank you so much for the extremely detailed explanation.
It sounds like a great system you have there. I was about to start work on a LOD system this evening, but per your suggestion i'll hold off until I see how it handles. I'll be sure to post screenshots once I get it up and running.

Once again, thanks for your help.