Now that we’ve drawn our first geometric shapes in Chapter 2, it’s time to step up the complexity a bit. While uploading our vertices to the GPU and rendering them all as a batch is a great solution for a single triangle, you will soon notice that as geometric complexity increases, so does the need for more efficient rendering methods.
In this chapter, you’ll learn how to:
Please note that this chapter builds on the samples provided in Chapter 2, so if you haven't read it, I suggest you read it now
Check the requirements before continuing
This chapter is 100% compatible with OpenGL 3.x level hardware by changing only a few lines of code.
Let's say we wanted to draw the following shape onto the screen, each of the spoke’s vertices (marked by Pn) a different color:
If we were to use the previous chapter’s code, we’d have to draw out 16
individual triangles composed of 48 vertices in total using the
primitive type since we describe each triangle separately. This means that each
set of three vertices in your array describes a single independent triangle. You
can see an example of this in file
chapter.3.0.1.c in the download section at
the end of this chapter.
In this example, P0 (origin) is duplicated 8 times in total, the center vertices P3, P7, P11, and P15 are duplicated 4 times each. If this seems like overkill to you for such a simple shape, you’re right; there are better ways of describing it.
Amount of data sent to GPU memory using
Vertex) 32 bytes x 48 = 1,536 bytes.
GL_TRIANGLE_STRIP primitive mode is a bit better when it comes to sheer
number of vertices sent to the GPU, since we only send 28 in total. However,
there’s still unneeded duplication, and the way in which we traverse the
vertices is a bit cumbersome due to the way that
GL_TRIANGLE_STRIP primitive type creates triangles out of every newly
added vertex and its preceding two vertices. In the animation, P0 to
P1 doesn’t yield a triangle, but after we add P3, a
triangle forms. Each vertex after the addition of P3 yields a new
In addition to the unneeded duplication (notice P3 → P4 → P3) and the amount of data sent over, there’s also no way of changing the order in which we draw the vertices besides uploading a completely new batch of data.
Amount of data sent to GPU memory using
Vertex) 32 bytes x 28 = 896 bytes.
So far, we’ve been drawing our geometry with a call to
simply draws a certain subset of elements from the currently active vertex
buffer object. Let’s explore a new way of drawing by walking through some new
code. Make a copy of
chapter.2.4.c from the previous chapter and name it
Since we’re in chapter two of the book, change the
pre-processor definition to reflect Chapter 3.
CurrentWidth to have an initial value of 600:
Add a new identifier named
IndexBufferId to our list of global identifier
Remove the call to
RenderFunction and replace it with the
Here’s the big change, change the
Vertices variable in
CreateVBO to the following:
Vertices, add the following array definition:
At the end of the
CreateVBO function, right below the last call to
glEnableVertexAttribArray, add the following lines of code:
Finally, add the following lines of code between the
glBindVertexArray lines of the
Your output should look like:
If you’ve analyzed the code a bit, you’ll notice that we upload each vertex exactly once to the GPU instead of 48 vertices including duplicates. Let’s walk through the code and take a good look at what we did here.
The first changes were purely aesthetic: as always, we changed the window title to reflect this chapter and made the window a perfect square for the geometry to show up symmetrical. In the next chapter, we’ll introduce a method that doesn’t require you to reshape your window to achieve symmetry.
The next thing we add is the
IndexBufferId variable to the global list of
variables. This variable will hold the buffer’s identifier generated by a call
glGenBuffers, similar to vertex buffer generation in the previous chapter.
After that, we modify the
CreateVBO function and replace its
with an array of 17 total vertices. If we were to use
glDrawArrays on this VBO
without indices, we would not output the desired shape as illustrated earlier.
Instead, we have to define one more array, named
Indices, to hold the indices
of the elements in
Vertices in drawing order.
For example, the first three indices 0, 1, and 3, correspond directly with the
first, fourth, and second elements in the
Vertices array, composing the
left-bottom triangle of the shape’s top spoke. In total, we upload 48 indices
since each spoke consists of four triangles (3 x 4 x 4).
Indices is defined as a
GLubyte array of 48 elements;
GLubyte is the
OpenGL data type for an unsigned byte (
unsigned char). You could use any of
the following unsigned integral OpenGL data types:
GLuint, since indices are never negative (signed) or fractional
The final thing inside of the
CreateVBO function is to generate the actual
index buffer with a familiar call to the
glGenBuffers, nothing new there. It
isn’t until the next function call to
glBindBuffers that we specify a brand
new target that we haven’t used before.
Until now, we’ve only supplied the
GL_ARRAY_BUFFER target to
specify that the buffer is an array of vertices. While we still upload our
vertices in this manner, our newly generated buffer is bound to the
GL_ELEMENT_ARRAY_BUFFER target that allows us to specify which vertices in the
GL_ARRAY_BUFFER we’re using.
The call to
glBufferData should also look very similar to last chapter’s code;
we introduce no new options here besides the usage of the
GL_ELEMENT_ARRAY_BUFFER target flag.
In the code, we removed the call to
glDrawArrays and replaced it with a call
to a function called
glDrawArrays only draws the
glDrawElements draws the indices of the active
GL_ARRAY_BUFFER as specified by the buffer bound to the
GL_ELEMENT_ARRAY_BUFFER target. Let’s take a closer look at the
The first parameter,
mode, takes in the primitive mode to use such as
GL_TRIANGLE_STRIP; the same as the
mode parameter of
The second parameter,
count, specifies how many elements in total to draw. In
our case, this value is 48 since that’s the amount of indices in the
The third parameter,
type, specifies which data type was used for the index
array. In our case, this is
GL_UNSIGNED_BYTE, since we used the
type to construct the
Indices array. Please note that this type must
reflect the data type used to construct the array containing the indices.
The fourth and last parameter,
indices, specifies the offset in bytes in the
index array of where we want to start rendering, allowing us to render subsets
of the vertex data. For example, if we wished to draw just the right-hand spoke
of the shape, we’d call the
glDrawElements function with the following parameters:
In this function call, we draw 12 vertices starting at index offset 36 of the
index buffer, corresponding to the vertices in the
Indices array. Make sure to
change the parameters in
glDrawElements according to the type of index buffer
you're using. For example, if we were to use an index buffer containing unsigned
GLuint), we'd have to change the above function call to the
Amount of data sent to GPU memory using Index Buffers:
Vertex) 32 bytes x 17 + 1
GLubyte x 48 = 592 bytes.
Sometimes it is not possible to avoid having to change the indices you wish to
render, in which case it is possible to swap the active index buffer by simply
changing the buffer bound to the
Make a copy of
chapter.3.1.c rename it to
chapter.3.2.c and open it up in your
editor. The first thing we do is change the block of global
definitions to look like this:
Next, add the following function declaration right underneath the
InitWindow function definition, add the following line underneath the
Add the following function definition right below the
Next, we need to make some changes to the rendering,
facilitate the changes we've made. Replace the call to glDrawElements with the
Inside of the
CreateVBO function right underneath the
definition, place the following code:
Inside of the same function, change the call to generate the index buffers to the following:
Immediately after that, change the
glBindBuffer function call to the
Right before the call to
glGetError, add the following block of code:
Finally, in the
DestroyVBO function, change the
glDeleteBuffers call with
IndexBufferId as its parameter to the following:
When you run the program and press the "T" key, you are able to toggle back and forth between the original shape and this new shape:
With only a few minor changes, we were able to draw an entirely new shape out of he same set of vertices. The only thing we did to achieve this was swap the index buffers. If you examined the code, you’ll notice that we’ve covered all of the functionality used in this sample before, so we’ll just glance over some of the highlights.
In this sample, we changed the amount of index buffers generated to two to
contain an alternate index buffer for our swapping purposes. In the
function, we updated
glGenBuffers to generate two buffer objects and store
their identifiers in the array
IndexBufferId, which now contains two elements.
After that, we upload the new indices stored in
AlternateIndices to the GPU’s
glBufferData as usual and set the current active index buffer
back to the original.
We also added some new FreeGLUT functionality for handling keyboard input:
Key parameter contains the character representation of the key pressed,
Y parameters contain the mouse positions relative to the
window at the time of the key-press. The only thing we do in this function is
toggle back and forth between index buffers while retaining the same vertex
We register the call to this function in the
InitWindow function with a call
glutKeyBoardFunc, which takes in as its only parameter a function pointer,
just as any other FreeGLUT callback functions do.
Until now, we’ve only discussed
methods to describe geometry. However, there are several more so-called
"primitive types" available in OpenGL, each of which alters the output in a
different way. In this section, we’ll discuss a few more.
A "primitive" is the smallest component of a geometrical shape. So far, we’ve used triangles as our primitive types but OpenGL supports two others: points and line segments.
The first and simplest primitive type is
GL_POINTS, where each vertex
specifies a visible point in space. When using
GL_POINTS, OpenGL will draw
simple points onto the screen. For example, change the
chapter.3.1.c to use
GL_POINTS instead of
GL_TRIANGLES, and each vertex
will show up as a colored one-pixel point.
You can change the point-size with the function
glPointSize, which simply
takes in a single parameter, size, specifying the size of the points as a
The second primitive type is
GL_LINE_STRIP, which allows us to draw lines
GL_LINE_STRIP works much like
each new vertex adds to the overall line instead of defining a brand new line
every two vertices.
To try this primitive type, add one more index to 0 at the very end of the index
chapter.3.1.c, and change the call to
to the following:
You should now see the outline of the shape described at the beginning of the chapter.
The third primitive type,
GL_LINE_LOOP is very similar to
the exception that it closes the line segment by drawing a line between the last
vertex and the first. Whereas we added one more index to
GL_LINE_STRIP, we would not have to do this with
GL_LINES is to lines what
GL_TRIANGLES is to triangles, meaning that
GL_LINES describes separate, unconnected lines. However, since a line consists
of two points instead of the three required by a triangle, changing the sample
chapter.3.1.c does not yield a desired result. We would have to modify the
index array extensively in order to get the correct results.
The last primitive type to discuss in this section is
GL_TRIANGLE_FAN, a close
relative to the well-known
constructs a new triangle by connecting the last three points added to the list,
GL_TRIANGLE_FAN constructs a new triangle from the very first point and the
last two points added, resulting in a fan-like shape. This means that every new
triangle is connected to the very first added to the list.
There is no correct way of drawing the shapes presented in this chapter using
GL_TRIANGLE_FAN, but this primitive type is useful for drawing center-oriented
polygons, such as a pentagon with all of its vertices connecting in the center.
If you’re looking to display your geometry as a wireframe of its original,
there’s no need to change its primitive type. The function
take care of this by changing the method used to fill the triangles, a process
called “rasterization” that we’ll explore in a future chapter:
The function’s first parameter,
face, specifies which polygons of your
geometry the function affects. As of OpenGL 3.0, this parameter may only be
GL_FRONT_AND_BACK; we’ll learn more about front- and back-faces in a
The function’s second parameter,
mode, specifies how the polygons are
rasterized. This mode can be set to one of the following values:
GL_POINTDraws the geometry as points, related to the
GL_LINEDraws the geometry as lines (wireframe), related to the
GL_FILLThe default: draws each triangle filled with a solid color.
Below follows a combination of three screenshots showing all of the different rasterization modes for the example shown earlier in this chapter:
Index Buffer Objects can be incredibly useful when dealing with complex shapes by limiting the amount of data sent to the GPU. The fact that many model formats provide their data in separate vertex and index sections only makes the decision to use index buffers more natural.
In upcoming chapters, index buffers are the primary method to describe geometry, so try to get familiar with this chapter and modify the samples to draw some geometry of your own. In the next chapter, we're drawing our first three-dimensional geometry.
You can find the source code for the samples in this chapter here.