Tutorial 2: Vertex Buffer Objects
Before starting this tute, make sure you have SDL working and can at least display a set of axes using OpenGL+SDL.
Use the following framework code which draws a wireframe grid using immediate mode and triangle strips.
Storing the Data
The grid is rendered using triangle strips in Immediate Mode. For this tute we will modify this to render using stored coordinates, then using stored coordinates and indices, then Vertex Arrays (VAs) and finally Vertex Buffer Objects (VBOs).
First modify the program to use display the grid using the stored vertex data. Study the way the coordinates are computed and stored. Notice that whilst the grid drawing code duplicates vertices for each unique position in the grid. each vertex coordinate is only stored once. Then if a coordinate needs to be used multiple times it can be accessed via its array index. Moreover, all the indices needed to render the grid can also be stored in an array - the index or element array. The size of the vertex array will need to be equal to the number of vertices (n_vertices), and is (rows+1) * (cols+1) (one unique vertex for each position). The size of the index array is basically the number of vertices * 2 (as vertices are repeated in neighbouring strips).
The program uses a vec3f struct that contains x,y,z floats, and a Vertex struct which has a vec3f for the vertex coordinates. Later we will add a normal to the Vertex struct, which could also contain other data such as texture coordinates also. The coordinate data is an array of Vertex, consisting of tightly packing float data.
After allocating the two arrays of the correct size, they are initialised with the correct data. It is fairly obvious what data should go into the vertex array, not quite as obvious what should go in the index array.
After changing the program to use the stored vertex data (which still uses Immediate Mode i.e. glVertex) try modifying it to use both the stored vertex and index data from the index and vertex arrays.
Vertex Arrays
Now that the arrays have been created, we can attempt to render them as vertex arrays:
-
Enable vertex array rendering by calling the
function
glEnableClientState(GL_VERTEX_ARRAY)
. -
Send the vertex data to OpenGL using the
function
glVertexPointer(3, GL_FLOAT, sizeof(Vertex), vertices)
.
If we were rendering triangles, we could send all the indices in one batch, however since we are rendering a series of triangle strips, multiple draw calls in a loop are used, eg:
for (int i = 0; i < cols; i++)
glDrawElements(GL_TRIANGLE_STRIP, (rows + 1) * 2, GL_UNSIGNED_INT, &indices[i * (rows + 1) * 2]);
A grid should render that matches the immediate mode grid.
The loop with multiple draw calls could be eliminated and
replaced by a single draw call
using glMultiDrawElements
, although internally this
may be implemented internally using a loop anyhow.
Note that although the number of vertices is the same, there is fewer indices using strips than individual triangles, It is not obvious which approach is faster, as unlike immediate mode, the vertices themselves are only processed once.
Vertex Buffer Objects
Now that we have VAs working correctly, we can attempt to render using VBOs. The advantage of this rendering technique is that vertex data does not need to be sent to the GPU each frame, only when we want to change it.
Rendering is performed in the same way as using Vertex Arrays, but in addition we need to do the following when creating the data:
-
Generate a VBO for the vertices using
glGenBuffers(1, &vbo)
(in the same way we would generate a texture object). -
Generate a VBO for the indices using
glGenBuffers(1, &ibo)
. -
Bind the vertex VBO using
glBindBuffer(GL_ARRAY_BUFFER, vbo)
(again, just as we would with a texture). -
Put data into the vertex VBO
using
glBufferData(GL_ARRAY_BUFFER, nVertices * sizeof(Vertex), vertices, GL_STATIC_DRAW)
(similar toglTexImage2D
). -
Bind the index VBO
using
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
. -
Put data into the index VBO using
glBufferData(GL_ELEMENT_ARRAY_BUFFER, nIndices * sizeof(unsigned int), indices, GL_STATIC_DRAW)
.
Now each time we want to render the VBO, we need to:
-
Bind the vertex VBO using
glBindBuffer(...)
. -
Set the vertex pointers as we would with a vertex array
using
glVertexPointer(...)
, however this time the last argument is an offset from the start of the VBO (position 0), ie:BUFFER_OFFSET(0)
whereBUFFER_OFFSET
can be written as:#define BUFFER_OFFSET(i) ((void*)(i))
. -
Bind the index VBO using
glBindBuffer(...)
. -
Render using
glDrawElements(...)
just as we would with a vertex array, but usingBUFFER_OFFSET(i * (rows + 1) * 2 * sizeof(unsigned int))
as the last argument.