Vertex Buffer Object

From OpenGL Wiki
Revision as of 13:30, 3 March 2012 by V-man (talk | contribs) (→‎Sample Code: Adding additional explanations to "Sample Code" section)
Jump to navigation Jump to search
Vertex Buffer Object
Core in version 4.6
Core since version 1.5
ARB extension GL_ARB_vertex_buffer_object

Vertex Buffer Objects (VBOs) are Buffer Objects that are used for vertex data. Since the storage for buffer objects is allocated by OpenGL, vertex buffer objects are a mechanism for storing vertex data in "fast" memory (i.e. video RAM or AGP RAM, and in the case of PCI Express, video RAM or RAM), thereby allowing for significant increases in vertex throughput between the application and the GPU.

Vertex data can be either vertex attributes and/or indices.

Legacy Note: Versions of OpenGL prior to 3.0 allowed the use of client-side data for vertex rendering, but GL 3.0 deprecated this. Core GL 3.1 and greater forbid client-side vertex data, so VBOs are the only means for rendering.

Inner Workings

Vertex Buffer Objects (VBOs) are just Buffer Objects used for storing vertex data. They are created and managed like any buffer object.

Sample Code

I suggest that you make a single VBO for your vertices (vertex, normals, texcoords, perhaps other things) and another VBO for indices (IBO). I suggest that you interleave your vertex attributes for best performance. The order of attributes does not matter for performance because it's just a pointer to a memory location for the GPU. The alternative is to make separate VBOs for each of your vertex attributes (vertex, normals, texcoords, perhaps other things). Most likely there will be a small performance loss, however, in some cases it can be useful. For example, if you will be changing your vertices on the CPU, then you can make a dynamic VBO for your vertices and a static VBO for your normals and texcoords.

http://www.opengl.org/wiki/VBO_-_more describes other vertex attribute formatting options.

Let's continue. Make a structure for your vertex attributes in your C++ code :

struct MyVertex
{
float x, y, z;        //Vertex
float nx, ny, nz;     //Normal
float s0, t0;         //Texcoord0
float s1, t1;         //Texcoord1
float s2, t2;         //Texcoord2
float padding[4];
};

The structure's size is 64 bytes. ATI suggests that you align your vertex size for 32 bytes, so I added 4 floats to make it 2 * 32 bytes. For nVidia, I think it doesn't matter

Generate a VBO for vertices (a vertex is not just a vertex. It is a collection of vertex attributes : vertex, normal, texcoord, etc). Make it STATIC, meaning we will never change it. It will be stored in VRAM:

 glGenBuffers(1, VertexVBOID);

GL_ARRAY_BUFFER is for vertices:

 glBindBuffer(GL_ARRAY_BUFFER, VertexVBOID);

The parameter SizeInBytes is how big you want your VBO to be, don't make it too small and don't make it too large. For good performance, allocate something like 1MB or 2MB or 4MB or 8MB and put many objects to be rendered into it. Notice that the pointer is NULL, which means we want GL to allocate memory but not initialize it. If you want to initialize, just pass a pointer to your array which holds the vertex data.

glBufferData(GL_ARRAY_BUFFER, SizeInBytes, NULL, GL_STATIC_DRAW);

Let's assume you used NULL. You can use glBufferSubData to upload all your vertices to the VBO or just upload some vertices. This is nice if you want to update only some of the vertices, but right now, we keep the example simple with lots of comments.

 MyVertex pvertices[XXX];
 //Fill the pvertices array
 pvertices[0].x=0.0;
 pvertices[0].y=0.0;
 pvertices[0].z=0.0;
 pvertices[0].nx=0.0;
 pvertices[0].ny=0.0;
 pvertices[0].nz=1.0;
 pvertices[0].s0=0.0;
 pvertices[0].t0=0.0;
 //etc....
 offsetInByte=0;
 glBufferSubData(GL_ARRAY_BUFFER, offsetInByte, SizeInBytes, &pvertices[0].x);

Now it's time to make a VBO for the indices. The same API for vertices is used but instead of calling it a VBO, we can call it a index buffer object - IBO.

 glGenBuffers(1, &IndexVBOID);
 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexVBOID);
 glBufferData(GL_ELEMENT_ARRAY_BUFFER, SizeInBytes, NULL, GL_STATIC_DRAW);
 ushort pindices[YYY];
 pindices[0]=0;
 pindices[1]=5;
 //etc...
 offsetInByte=0;
 glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offsetInByte, SizeInBytes, pindices);

For best performance, they suggest that you used 16 bit unsigned integers (short).
32 bit unsigned integer is the other option.
Don't use unsigned byte indices unless you're certain you always have <= 256 vertices.

Now it's time to render. I have not included the part about setting up shaders or binding shaders.

Make the following definition in your C++ code:

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

Bind your VBO. P.S., Our VBO is already bound actually.

glBindBuffer(GL_ARRAY_BUFFER, VertexVBOID);

Call your gl***Pointer functions to make a INTERLEAVED ARRAY. The pointers are relative to the zero point of the VBO. Therefore, the first vertex is at byte offset 0. The first normal is at byte offset 12. The first texcoord0 is at byte offset 24. The first texcoord1 is at byte offset 32. The first texcoord2 is at byte offset 40.

You may have noticed that we set the stride to 64 bytes. That is the size of a single vertex struct. It means that the second vertex is 64 bytes away from the zero point of the VBO. The second vertex is 64+64 bytes away from the zero point of the VBO. The first normal is at 12+64 bytes away from the zero point of the VBO. Instead of typing in 64 for your stride parameter, you might want to use the sizeof keyword in C++ : sizeof(MyVertex).

All that may seem like complex math to you but GPUs are designed for it. You might also want to read about the pre-transform and post-transform caches on GPUs and the sizes of those caches and the effects of your vertex structure size on performance. Also, read about how to organize your indices for best performance. There are tools that analyze and reorganize your indices.

Let's continue :

glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glVertexPointer(3, GL_FLOAT, 64, BUFFER_OFFSET(0));
glNormalPointer(GL_FLOAT, 64, BUFFER_OFFSET(12));
glClientActiveTexture(GL_TEXTURE0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);    //Notice that after we call glClientActiveTexture, we enable the array
glTexCoordPointer(2, GL_FLOAT, 64, BUFFER_OFFSET(24));
glClientActiveTexture(GL_TEXTURE1);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);    //Notice that after we call glClientActiveTexture, we enable the array
glTexCoordPointer(2, GL_FLOAT, 64, BUFFER_OFFSET(32));
glClientActiveTexture(GL_TEXTURE2);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);    //Notice that after we call glClientActiveTexture, we enable the array
glTexCoordPointer(2, GL_FLOAT, 64, BUFFER_OFFSET(40));

Bind your IBO. P.S., Our IBO is already bound actually.

 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexVBOID);

Draw and pass the pointer relative to the zero point of the IBO.

 glDrawRangeElements(GL_TRIANGLES, x, y, z, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));

Feel free to now call glDisableClientState.

When you are done using the VBO, release the handles

 glDeleteBuffers(1, &VertexVBOID);
 glDeleteBuffers(1, &IndexVBOID);

In summary:

  • Make your vertex structure.
    Make it multiple of 32 bytes in size.
  • Make a VBO.
  • Make an IBO.
    Use 16 bit integer.
  • Try not to make many redundant calls to GL to maximize performance.
  • Don't use glInterleaved array because almost nobody uses it and it's limited. You can read about it here: Common Mistakes

I haven't talked about glMapBuffer and glUnmapBuffer, but they're easy to use.

Tips and tricks

  • Mapping a buffer may cause your data to be lost.
  • Array pointers are only treated as offsets if a buffer object is bound. Binding 0 returns to normal behaviour (i.e. direct pointers).
  • Multiple buffers can be mapped simultaneously.
  • All vertex attributes do not necessarily need to be in the same buffer object.
  • Calling glBufferData with a NULL pointer before uploading new data can improve performance (tells the driver you don't care about the old contents)

See Also

Reference