lid6j86's picture

VBO instancing example

I was discussing with dimension314 about it, and decided to post the question since it seems he gets quite a few similar questions. The question was basically the best method for rendering with VBOs. The situation is that pretty much any tutorial you see will teach you how to render a single triangle, or a single cube, etc.... but there isn't a lot of information that discusses efficient rendering of multiple instances of any given VBO.

by way of example if you have a VBO template of a monster, but you need 20 monsters to be rendered. Does that warrent 20 different VBOs for each monster? seems inneficient and like it will quickly bog down with all the calls to opengl. So the discussion revolved around a practical example of VBOs being created, stored, cand called for many instances. Essentially buffers in greater context


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
dimension314's picture

For VBO's for a voxel engine, an important thing to remember is that you only want to render the faces that are actually visible. So if 2 blocks are side by side for example, you don't want to render the face of each that is touching the other (the one exception to that is if one of them is transparent, such as leaves or water). So when you build your VBO's you want logic to decide which faces to add to it. Because you're not drawing anything for air blocks or touching faces, this reduces the number of vertices needed in the VBO by a huge percentage which also reduces the memory consumption and increases fps.

Another important point. You will probably need to separately store the same vertexes even if you draw more than one face with the same vertex, because for lighting for example, each vertex gets a different color value based partially on the direction its facing and the light from neighbors. Alternatively you may want to store normals with your vertices which are also different depending on the direction they face. Originally I was using normals but decided to scrap them in favor of colors.

In the end you have a VBO for each chunk / texture combo. If you determine a chunk needs to be rendered you simply loop through its VBO's and render each one. This is a very efficient way to render (without using shaders anyway) because once the VBO's are loaded, the number of GL calls is very small (binding a texture and buffers and then calling GL.DrawArrays in my case).

In my VBO's I store vertex, color and texture coords using float, unsigned byte and short respectively. So the memory consumed is as small as possible.

The actual code for rendering looks like this:

//bind your texture here
 
//position array buffer
GL.BindBuffer(BufferTarget.ArrayBuffer, _positionId);
GL.VertexPointer(3, VertexPointerType.Float, Vector3.SizeInBytes, IntPtr.Zero);
 
//color array buffer
GL.BindBuffer(BufferTarget.ArrayBuffer, _colorId);
GL.ColorPointer(3, ColorPointerType.UnsignedByte, ColorRgb.SIZE, IntPtr.Zero);
 
//texCoord array buffer
GL.BindBuffer(BufferTarget.ArrayBuffer, _texCoordId);
GL.TexCoordPointer(2, TexCoordPointerType.Short, TexCoordsShort.SIZE, IntPtr.Zero);
 
GL.DrawArrays(BeginMode.Quads, 0, PrimitiveCount);
mOfl's picture

As you already mentioned right in the topic, "instancing" is the magic word that you should look up for a bunch of different solutions. The most interesting results can be found under the term "hardware instancing". Instancing basically says that you collect all transformation data and instance attributes before drawing, send them all to the video card and finally draw them in a single call. You can, of course, use the hardware and OpenGL API to help you with instancing, because it is a common task to do and mechanisms have been implemented for this. There are two ways I'd recommend you to try:
1) Use the geometry shader. In the geometry shader, for each vertex you can emit additional vertices and transform them the way you want.
2) Use GL.DrawElementsInstanced(). This does the multiple calling internally for you and provides a gl_InstanceID variable in the vertex shader such that you only need to provide a uniform array, buffer, or texture with all the instance data and transformations stored for each instance, use the instance ID to grab the right information and do the stuff in the vertex shader as usual.