- OpenGL Graphics Primitives
- Data in OpenGL Buffers
- Vertex Specification
- OpenGL Drawing Commands
- Instanced Rendering
OpenGL Drawing Commands
Most OpenGL drawing commands start with the word Draw.7 The drawing commands are roughly broken into two subsets—indexed and nonindexed draws. Indexed draws use an array of indices stored in a buffer object bound to the GL_ELEMENT_ARRAY_BUFFER binding that is used to indirectly index into the enabled vertex arrays. On the other hand, nonindexed draws do not use the GL_ELEMENT_ARRAY_BUFFER at all, and simply read the vertex data sequentially. The most basic, nonindexed drawing command in OpenGL is glDrawArrays().
Similarly, the most basic indexed drawing command is glDrawElements().
Each of these functions causes vertices to be read from the enabled vertex-attribute arrays and used to construct primitives of the type specified by mode. Vertex-attribute arrays are enabled using glEnableVertexAttribArray() as described in Chapter 1. glDrawArrays() just uses the vertices in the buffer objects associated with the enabled vertex attributes in the order they appear. glDrawElements() uses the indices in the element array buffer to index into the vertex attribute arrays. Each of the more complex OpenGL drawing functions essentially builds functionality on top of these two functions. For example, glDrawElementsBaseVertex() allows the indices in the element array buffer to be offset by a fixed amount.
glDrawElementsBaseVertex() allows the indices in the element array buffer to be interpreted relative to some base index. For example, multiple versions of a model (say, frames of an animation) can be stored in a single set of vertex buffers at different offsets within the buffer. glDrawElementsBaseVertex() can then be used to draw any frame of that animation by simply specifying the first index that corresponds to that frame. The same set of indices can be used to reference every frame.
Another command that behaves similarly to glDrawElements() is glDrawRangeElements().
Various combinations of functionality are available through even more advanced commands—for example, glDrawRangeElementsBaseVertex() combines the features of glDrawElementsBaseVertex() with the contractual arrangement of glDrawRangeElements().
Instanced versions of both of these functions are also available. Instancing will be covered in “Instanced Rendering” on Page 128. The instancing commands include glDrawArraysInstanced(), glDrawElementsInstanced(), and even glDrawElementsInstancedBaseVertex(). Finally, there are two commands that take their parameters not from your program directly, but from a buffer object. These are the draw-indirect functions, and to use them, a buffer object must be bound to the GL_DRAW_INDIRECT_BUFFER binding. The first is the indirect version of glDrawArrays(), glDrawArraysIndirect().
In glDrawArraysIndirect(), the parameters for the actual draw command are taken from a structure stored at offset indirect into the draw indirect buffer. The structure’s declaration in “C” is presented in Example 3.3:
Example 3.3. Declaration of the DrawArraysIndirectCommand Structure
typedef struct
DrawArraysIndirectCommand_t
{
GLuint count;
GLuint primCount;
GLuint first;
GLuint baseInstance;
} DrawArraysIndirectCommand;
The fields of the DrawArraysIndirectCommand structure are interpreted as if they were parameters to a call to glDrawArraysInstanced(). first and count are passed directly to the internal function. The primCount field is the instance count, and the baseInstance field becomes the baseInstance offset to any instanced vertex attributes (don’t worry, the instanced rendering commands will be described shortly).
The indirect version of glDrawElements() is glDrawElementsIndirect() and its prototype is as follows:
As with glDrawArraysIndirect(), the parameters for the draw command in glDrawElementsIndirect() come from a structure stored at offset indirect stored in the element array buffer. The structure’s declaration in “C” is presented in Example 3.4:
Example 3.4. Declaration of the DrawElementsIndirectCommand Structure
typedef struct
DrawElementsIndirectCommand_t
{
GLuint count;
GLuint primCount;
GLuint firstIndex;
GLuint baseVertex;
GLuint baseInstance;
} DrawElementsIndirectCommand;
As with the DrawArraysIndirectCommand structure, the fields of the DrawElementsIndirectCommand structure are also interpreted as calls to the glDrawElementsInstancedBaseVertex() command. count and baseVertex are passed directly to the internal function. As in glDrawArraysIndirect(), primCount is the instance count. firstVertex is used, along with the size of the indices implied by the type parameter to calculate the value of indices that would have been passed to glDrawElementsInstancedBaseVertex(). Again, baseInstance becomes the instance offset to any instanced vertex attributes used by the resulting drawing commands.
Now, we come to the drawing commands that do not start with Draw. These are the multivariants of the drawing commands, glMultiDrawArrays(), glMultiDrawElements(), and glMultiDrawElementsBaseVertex(). Each one takes an array of first parameters, and an array of count parameters acts as if the nonmultiversion of the function had been called once for each element of the array. For example, look at the prototype for glMultiDrawArrays().
Calling glMultiDrawArrays() is equivalent to the following OpenGL code sequence:
void
glMultiDrawArrays(GLenum mode,const
GLint * first,const
GLint * count, GLsizei primcount) { GLsizei i;for
(i = 0; i < primcount; i++) { glDrawArrays(mode, first[i], count[i]); } }
Similarly, the multiversion of glDrawElements() is glMultiDrawElements(), and its prototype is as follows:
Calling glMultiDrawElements() is equivalent to the following OpenGL code sequence:
void
glMultiDrawElements(GLenum mode,const
GLsizei * count, GLenum type,const
GLvoid *const
* indices, GLsizei primcount); { GLsizei i;for
(i = 0; i < primcount; i++) { glDrawElements(mode, count[i], type, indices[i]); } }
An extension of glMultiDrawElements() to include a baseVertex parameter is glMultiDrawElementsBaseVertex(). Its prototype is as follows:
As with the previously described OpenGL multidrawing commands, glMultiDrawElementsBaseVertex() is equivalent to another code sequence that ends up calling the nonmultiversion of the function.
void
glMultiDrawElementsBaseVertex(GLenum mode,const
GLsizei * count, GLenum type,const
GLvoid *const
* indices, GLsizei primcount,const
\GLint * baseVertex); { GLsizei i;for
(i = 0; i < primcount; i++) { glDrawElements(mode, count[i], type, indices[i], baseVertex[i]); } }
Finally, if you have a large number of draws to perform and the parameters are already in a buffer object suitable for use by glDrawArraysIndirect() or glDrawElementsIndirect(), it is possible to use the multi versions of these two functions, glMultiDrawArraysIndirect() and glMultiDrawElementsIndirect().
OpenGL Drawing Exercises
This is a relatively simple example of using a few of the OpenGL drawing commands covered so far in this chapter. Example 3.5 shows how the data is loaded into the buffers required to use the draw commands in the example. Example 3.6 shows how the drawing commands are called.
Example 3.5. Setting up for the Drawing Command Example
// A four verticesstatic const
GLfloat vertex_positions[] = { -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, }; // Color for each vertexstatic const
GLfloat vertex_colors[] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f }; // Three indices (we’re going to draw one triangle at a timestatic const
GLushort vertex_indices[] = { 0, 1, 2 }; // Set up the element array buffer glGenBuffers(1, ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]); glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof
(vertex_indices), vertex_indices, GL_STATIC_DRAW); // Set up the vertex attributes glGenVertexArrays(1, vao); glBindVertexArray(vao[0]); glGenBuffers(1, vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); glBufferData(GL_ARRAY_BUFFER,sizeof
(vertex_positions) +sizeof
(vertex_colors), NULL, GL_STATIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0,sizeof
(vertex_positions), vertex_positions); glBufferSubData(GL_ARRAY_BUFFER,sizeof
(vertex_positions),sizeof
(vertex_colors), vertex_colors);
Example 3.6. Drawing Commands Example
// DrawArrays model_matrix = vmath::translation(-3.0f, 0.0f, -5.0f); glUniformMatrix4fv(render_model_matrix_loc, 4, GL_FALSE, model_matrix); glDrawArrays(GL_TRIANGLES, 0, 3); // DrawElements model_matrix = vmath::translation(-1.0f, 0.0f, -5.0f); glUniformMatrix4fv(render_model_matrix_loc, 4, GL_FALSE, model_matrix); glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, NULL); // DrawElementsBaseVertex model_matrix = vmath::translation(1.0f, 0.0f, -5.0f); glUniformMatrix4fv(render_model_matrix_loc, 4, GL_FALSE, model_matrix); glDrawElementsBaseVertex(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, NULL, 1); // DrawArraysInstanced model_matrix = vmath::translation(3.0f, 0.0f, -5.0f); glUniformMatrix4fv(render_model_matrix_loc, 4, GL_FALSE, model_matrix); glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 1);
The result of the program in Examples 3.5 and 3.6 is shown in Figure 3.5. It’s not terribly exciting, but you can see four similar triangles, each rendered using a different drawing command.
Figure 3.5. Simple example of drawing commands
Restarting Primitives
As you start working with larger sets of vertex data, you are likely to find that you need to make numerous calls to the OpenGL drawing routines, usually rendering the same type of primitive (such as GL_TRIANGLE_STRIP) that you used in the previous drawing call. Of course, you can use the glMultiDraw*() routines, but they require the overhead of maintaining the arrays for the starting index and length of each primitive.
OpenGL has the ability to restart primitives within the same drawing command by specifying a special value, the primitive restart index, which is specially processed by OpenGL. When the primitive restart index is encountered in a draw call, a new rendering primitive of the same type is started with the vertex following the index. The primitive restart index is specified by the glPrimitiveRestartIndex() function.
As vertices are rendered with one of the glDrawElements() derived function calls, it can watch for the index specified by glPrimitiveRestartIndex() to appear in the element array buffer. However, it watches only for this index to appear if primitive restating is enabled. Primitive restarting is controlled by calling glEnable() or glDisable() with the GL_PRIMITIVE_RESTART parameter.
To illustrate, consider the layout of vertices in Figure 3.6, which shows how a triangle strip would be broken in two by using primitive restarting. In this figure, the primitive restart index has been set to 8. As the triangles are rendered, OpenGL watches for the index 8 to be read from the element array buffer, and when it sees it go by, rather than creating a vertex, it ends the current triangle strip. The next vertex (vertex 9) becomes the first vertex of a new triangle strip, and so in this case two triangle strips are created.
Figure 3.6. Using primitive restart to break a triangle strip
The following example demonstrates a simple use of primitive restart—it draws a cube as a pair of triangle strips separated by a primitive restart index. Examples 3.7 and 3.8 demonstrate how the data for the cube is specified and then drawn.
Example 3.7. Intializing Data for a Cube Made of Two Triangle Strips
// 8 corners of a cube, side length 2, centered on the originstatic const
GLfloat cube_positions[] = { -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; // Color for each vertexstatic const
GLfloat cube_colors[] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f }; // Indices for the triangle stripsstatic const
GLushort cube_indices[] = { 0, 1, 2, 3, 6, 7, 4, 5, // First strip 0xFFFF, // <<-- This is the restart index 2, 6, 0, 4, 1, 5, 3, 7 // Second strip }; // Set up the element array buffer glGenBuffers(1, ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]); glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof
(cube_indices), cube_indices, GL_STATIC_DRAW); // Set up the vertex attributes glGenVertexArrays(1, vao); glBindVertexArray(vao[0]); glGenBuffers(1, vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); glBufferData(GL_ARRAY_BUFFER,sizeof
(cube_positions) +sizeof
(cube_colors), NULL, GL_STATIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0,sizeof
(cube_positions), cube_positions); glBufferSubData(GL_ARRAY_BUFFER,sizeof
(cube_positions),sizeof
(cube_colors), cube_colors); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (const
GLvoid *)sizeof
(cube_positions)); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1);
Figure 3.7 shows how the vertex data given in Example 3.7 represents the cube as two independent triangle strips.
Figure 3.7. Two triangle strips forming a cube
Example 3.8. Drawing a Cube Made of Two Triangle Strips Using Primitive Restart
// Set up for a glDrawElements call glBindVertexArray(vao[0]); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]);#if
USE_PRIMITIVE_RESTART // When primitive restart is on, we can call one draw command glEnable(GL_PRIMITIVE_RESTART); glPrimitiveRestartIndex(0xFFFF); glDrawElements(GL_TRIANGLE_STRIP, 17, GL_UNSIGNED_SHORT, NULL);#else
// Without primitive restart, we need to call two draw commands glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, NULL); glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_SHORT, (const
GLvoid *)(9 *sizeof
(GLushort))); #endif