- OpenGL Graphics Primitives
- Data in OpenGL Buffers
- Vertex Specification
- OpenGL Drawing Commands
- Instanced Rendering
OpenGL Drawing Commands
大多数OpenGL绘制命令从单词开始Draw.7The 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 isglDrawArrays().
Similarly, the most basicindexeddrawing command isglDrawElements().
Each of these functions causes vertices to be read from the enabled vertex-attribute arrays and used to construct primitives of the type specified bymode. Vertex-attribute arrays are enabled usingglEnableVertexAttribArray()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()可以用来画框架,animati吗on 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 toglDrawElements()isglDrawRangeElements().
Various combinations of functionality are available through even more advanced commands—for example,glDrawRangeElementsBaseVertex()combines the features ofglDrawElementsBaseVertex()with the contractual arrangement ofglDrawRangeElements().
Instancedversions of both of these functions are also available. Instancing will be covered in “Instanced Rendering” on Page 128. The instancing commands includeglDrawArraysInstanced(),glDrawElementsInstanced(), and evenglDrawElementsInstancedBaseVertex(). Finally, there are two commands that take their parameters not from your program directly, but from abuffer 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 ofglDrawArrays(),glDrawArraysIndirect().
InglDrawArraysIndirect(), the parameters for the actual draw command are taken from a structure stored at offsetindirectinto thedraw indirectbuffer. 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 theDrawArraysIndirectCommandstructure are interpreted as if they were parameters to a call toglDrawArraysInstanced().firstand数are passed directly to the internal function. TheprimCountfield is the instance count, and thebaseInstancefield becomes thebaseInstanceoffset to any instanced vertex attributes (don’t worry, the instanced rendering commands will be described shortly).
The indirect version ofglDrawElements()isglDrawElementsIndirect()and its prototype is as follows:
As withglDrawArraysIndirect(), the parameters for the draw command inglDrawElementsIndirect()come from a structure stored at offsetindirectstored 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 theDrawArraysIndirectCommandstructure, the fields of theDrawElementsIndirectCommandstructure are also interpreted as calls to theglDrawElementsInstancedBaseVertex()command.数andbaseVertexare passed directly to the internal function. As inglDrawArraysIndirect(),primCountis the instance count.firstVertexis used, along with the size of the indices implied by thetypeparameter to calculate the value ofindicesthat would have been passed toglDrawElementsInstancedBaseVertex(). Again,baseInstancebecomes 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 withDraw. These are the multivariants of the drawing commands,glMultiDrawArrays(),glMultiDrawElements(), andglMultiDrawElementsBaseVertex(). Each one takes an array offirstparameters, and an array of数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 forglMultiDrawArrays().
CallingglMultiDrawArrays()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 ofglDrawElements()isglMultiDrawElements(), and its prototype is as follows:
CallingglMultiDrawElements()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 ofglMultiDrawElements()to include abaseVertexparameter isglMultiDrawElementsBaseVertex(). 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 byglDrawArraysIndirect()orglDrawElementsIndirect(), it is possible to use themultiversions of these two functions,glMultiDrawArraysIndirect()andglMultiDrawElementsIndirect().
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 inFigure 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, theprimitive 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 theglPrimitiveRestartIndex()function.
As vertices are rendered with one of theglDrawElements()derived function calls, it can watch for the index specified byglPrimitiveRestartIndex()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 callingglEnable()orglDisable()with the GL_PRIMITIVE_RESTART parameter.
To illustrate, consider the layout of vertices inFigure 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.7shows 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