Please send suggestions or corrections for this page to the author at sbuss@ucsd.edu.
This page describes some of the features of Modern OpenGL used in the BasicDrawModes program. This is part of a series of programs illustrating features of Modern OpenGL. The first two programs, SimpleDrawModern and SimpleAnimModern, already introduced many of the basic features of any OpenGL program, and these were described on their web pages. On this page, we only discuss aspects of the software with are new to BasicDrawModes.
The code fragments listed below are taken from the programs BasicDrawModes.cpp. It is recommended to refer to the source code as you read this web page to see how the code fits together. In some cases, the code examples given below are slightly modified to make this web page more readable. We cannot cover all features of commands here, and it strongly recommended to your browser search to find on-line documentation for the OpenGL commands and keywords to learn more about their functionality.
BasicDrawModes draws the same geometry four times
using different methods available in Modern OpenGL for rendering.
OpenGL has a lot of different draw modes. The four
drawing modes illustrated in BasicDrawModes are among the
simplest drawing methods available. More advanced drawing modes
include the "instanced" and "indirect" modes, but these
are not covered here. Nor is the "immediate" draw mode of
legacy OpenGL. Some of these may be covered in future
programs; for instance, see FourTexturesModern
for an example of how to use glDrawElementsBaseVertex
.
This drawing mode was already used in SimpleAnimModern
and SimpleAnimModern, but we cover it again here to
contrast with the other drawing modes. The routine
mySetupGeometries
contains the following
initialization:
// FIRST GEOMETRY: Drawn with glDrawArrays.
float firstGeom[][3] = {
{ 0.0f, 3.0f, 0.0 }, { 0.0f, 2.0f, 0.0 }, { 1.0f, 3.0f, 0.0 }, { 1.0f, 2.0f, 0.0 },
{ 2.0f, 3.0f, 0.0 }, { 2.0f, 2.0f, 0.0 }, { 3.0f, 3.0f, 0.0 }, { 3.0f, 2.0f, 0.0 },
{ 0.0f, 2.0f, 0.0 }, { 0.0f, 1.0f, 0.0 }, { 1.0f, 2.0f, 0.0 }, { 1.0f, 1.0f, 0.0 },
{ 2.0f, 2.0f, 0.0 }, { 2.0f, 1.0f, 0.0 }, { 3.0f, 2.0f, 0.0 }, { 3.0f, 1.0f, 0.0 },
{ 0.0f, 1.0f, 0.0 }, { 0.0f, 0.0f, 0.0 }, { 1.0f, 1.0f, 0.0 }, { 1.0f, 0.0f, 0.0 },
{ 2.0f, 1.0f, 0.0 }, { 2.0f, 0.0f, 0.0 }, { 3.0f, 1.0f, 0.0 }, { 3.0f, 0.0f, 0.0 },
};
// Bind (and initialize) the Vertex Array Object and its associated buffer objects
glBindVertexArray(myVAO[vaoDrawArrays]);
glBindBuffer(GL_ARRAY_BUFFER, myVBO[vboDrawArrays]);
glBufferData(GL_ARRAY_BUFFER, sizeof(firstGeom), firstGeom, GL_STATIC_DRAW);
glVertexAttribPointer(vertPos_loc, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); // Info about where positions are in the VBO
glEnableVertexAttribArray(vertPos_loc); // Enable the stored vertex positions
The routine myRenderScene
renders
this as three triangle strips with the code:
glBindVertexArray(myVAO[vaoDrawArrays]);
glVertexAttrib3f(vertColor_loc, 1.0f, 0.4f, 0.4f); // A light red color
theModelViewMatrix.Set_glTranslate(1.0, 1.0, 0.0);
theModelViewMatrix.DumpByColumns(matEntries);
glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 8);
glDrawArrays(GL_TRIANGLE_STRIP, 8, 8);
glDrawArrays(GL_TRIANGLE_STRIP, 16, 8);
There are 16 unique vertices specified in the array firstGeom
.
However, since they are used to form three triangle strips with eight vertices each,
there are a total of 24 vertex positions specified in the array. This causes a
duplication of eight vertices. In addition, the vertices must be placed into
firstGeom
in the order needed for drawing the triangle strips; this
is a rather counter-intuitive way to order vertices. The next draw mode will address
these issues.
For the second geometry (which renders identically to the first geometry),
the initialization in mySetupGeometries
is:
float otherGeom[][3] = {
{ 0.0f, 3.0f, 0.0 }, { 1.0f, 3.0f, 0.0 }, { 2.0f, 3.0f, 0.0 }, { 3.0f, 3.0f, 0.0 },
{ 0.0f, 2.0f, 0.0 }, { 1.0f, 2.0f, 0.0 }, { 2.0f, 2.0f, 0.0 }, { 3.0f, 2.0f, 0.0 },
{ 0.0f, 1.0f, 0.0 }, { 1.0f, 1.0f, 0.0 }, { 2.0f, 1.0f, 0.0 }, { 3.0f, 1.0f, 0.0 },
{ 0.0f, 0.0f, 0.0 }, { 1.0f, 0.0f, 0.0 }, { 2.0f, 0.0f, 0.0 }, { 3.0f, 0.0f, 0.0 },
};
// EBO data for glDrawElements and glMultiDrawElements
unsigned int drawmultidrawElts[] = {
0, 4, 1, 5, 2, 6, 3, 7,
4, 8, 5, 9, 6, 10, 7, 11,
8, 12, 9, 13, 10, 14, 11, 15
};
....
glBindVertexArray(myVAO[vaoDrawElements]);
glBindBuffer(GL_ARRAY_BUFFER, myVBO[vaoDrawElements]);
glBufferData(GL_ARRAY_BUFFER, sizeof(otherGeom), otherGeom, GL_STATIC_DRAW);
glVertexAttribPointer(vertPos_loc, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(vertPos_loc);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, myEBO[eboDrawMultiDrawElts]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(drawmultidrawElts), drawmultidrawElts, GL_STATIC_DRAW);
Now the vertices in the array otherGeom
are listed in rectangular order,
and each vertex is listed only once. The array drawmultidrawElts
contains the indices of the vertices needed for the triangle strips.
For example, the first triangle strip starts with vertex 0, then vertex 4, ...,
ending with vertex 3 and finally vertex 7.
These vertex values refer to the positions
of the vertices in the array otherGeom
. The three triangle strips
are rendered in myRenderScene
with:
glBindVertexArray(myVAO[vaoDrawElements]);
glVertexAttrib3f(vertColor_loc, 1.0f, 1.0f, 0.4f); // A light yellow color
theModelViewMatrix.Set_glTranslate(5.0, 1.0, 0.0);
theModelViewMatrix.DumpByColumns(matEntries);
glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_INT, 0);
glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_INT, (void*)(8 * sizeof(unsigned int)));
glDrawElements(GL_TRIANGLE_STRIP, 8, GL_UNSIGNED_INT, (void*)(16*sizeof(unsigned int)));
The second argument to glDrawElements
gives the
number (8 for us) of vertices in the triangle strip. The fourth
argument gives the displacement (aka "offset") in bytes to the
locations holding the elements (indices of the vertices) for the triangle
strip. In the code above, the first one starts at position 0,
the second starts at element 8, and the third starts at element 16.
The third argument, GL_USIGNED_INT
specifies the data
type used for the elements: that is, drawmultidrawElts
is an array of unsigned integers.
In our simple example, there was no net savings of memory in
using glDrawElements
instead of glDrawArrays
;
however, vertices often contain other attributes besides positions
such as surface normals or texture coordinates.
In this case, there can be substantial memory savings from the use of
glDrawElements
.
A disadvantage of the two drawing modes discussed so far is
that glDrawArrays
or glDrawElements
must be called once per primitive (e.g., once per triangle strip).
Calls to these routines have a lot overhead, and slow down
the rendering. The next two drawing modes give ways to avoid
having multiple calls.
A single call to glMultiDrawElements
is used to
simulate the effect of multiple calls to glDrawElements
.
The varying parameters to glDrawElements
are taken
from two arrays, multidrawCount
and multidrawStartIdx
.
mySetupGeometries
sets up the VAO for using glMultiDrawElements
exactly the same way that the VAO was set up in the previous code
for using multiple calls to glDrawElements
. The only difference
is that this time there are no calls glBufferData
: this is
because the vertex position data and the element array
data was already loaded into the buffer by the earlier
calls to glBufferData
. In other words,
the two VAO's can use the same VBO buffers!
// Do not need to give the glBufferData commands again. Data was already loaded into the buffer.
glBindVertexArray(myVAO[vaoMultiDrawElements]);
glBindBuffer(GL_ARRAY_BUFFER, myVBO[vboDrawEltsAll]);
glVertexAttribPointer(vertPos_loc, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(vertPos_loc);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, myEBO[eboDrawMultiDrawElts]);
The following two arrays are defined as global variables
to specify parameters of the type sent to glDrawElements
.
// Each of the triangle strips has 8 vertices
int multidrawCount[3] = { 8, 8, 8 };
// The starting positions in the EBO of the three triangle strips
void* multidrawStartIdx[3] = { 0, (void*)(8 * sizeof(unsigned int)), (void*)(16 * sizeof(unsigned int)) };
myRenderScene
renders this geometry with the
commands:
glBindVertexArray(myVAO[vaoMultiDrawElements]);
glVertexAttrib3f(vertColor_loc, 0.6f, 0.6f, 1.0f); // A light blue color
theModelViewMatrix.Set_glTranslate(1.0, 5.0, 0.0);
theModelViewMatrix.DumpByColumns(matEntries);
glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
glMultiDrawElements(GL_TRIANGLE_STRIP, multidrawCount, GL_UNSIGNED_INT, multidrawStartIdx, 3);
The final parameter (3) of glMultiDrawElements
indicates the number
of triangle strips to draw. The arrays multidrawCount
and
multidrawStartIdx
have length 3: multidrawCount[i]
specifies the number of triangles in the i
-th
triangle strip, and multidrawStartIdx[i]
specifies
position in the EBO of the first element for
the i
-th triangle strip. In this example, these values
are identical to those used for the calls to glDrawArrays
in Section II above.
Remark:
The code above used a different VAO, myVAO[vaoMultiDrawElements]
.
This is actually optional in this program:
The same call to glMultiDrawElements
will work with myVAO[vaoDrawElements]
.
Another elegant way to render multiple triangle strips
with a single call is to use glDrawElements
with a special element value indicating a
primitive restart. A value
restartPrimitiveCode
is used to indicate
the break between two triangle fans.
BasicDrawModes defines the value of restartPrimitiveCode
to equal 0xfffffff
, but all that
is really important is that it is not a valid element
(i.e., not an index for a vertex). The array
drawPrimRestartElts
holds the complete list
of elements needed for the three triangle strips, using
restartPrimitiveCode
to separate the
triangle strips:
mySetupGeometries
sets up data
for the EBO and VAO with the following code:
unsigned int restartPrimitiveCode = 0xffffffff; // Max. unsigned integer
…
// EBO data for glDrawElementsPrimitiveRestart
// Three triangle strips with eight vertices each.
// Breaks between triangle strips indicated by restartPrimitiveCode.
unsigned int drawPrimRestartElts[] = {
0, 4, 1, 5, 2, 6, 3, 7, restartPrimitiveCode,
4, 8, 5, 9, 6, 10, 7, 11, restartPrimitiveCode,
8, 12, 9, 13, 10, 14, 11, 15
};
...
glBindVertexArray(myVAO[vaoDrawElementsPrimitiveRestart]);
glBindBuffer(GL_ARRAY_BUFFER, myVBO[vboDrawEltsAll]);
glVertexAttribPointer(vertPos_loc, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(vertPos_loc);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, myEBO[eboDrawEltsPrimRestart]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(drawPrimRestartElts), drawPrimRestartElts, GL_STATIC_DRAW);
Note that there is again no call to glBufferData
for the VBO,
the VBO already contains the vertex data.
However, there is a call to glBufferData
for the new
array drawPrimRestartElts
.
myRenderScene
renders this geometry with the
commands:
glBindVertexArray(myVAO[vaoDrawElementsPrimitiveRestart]);
glVertexAttrib3f(vertColor_loc, 0.4f, 1.0f, 0.4f); // A light green color
theModelViewMatrix.Set_glTranslate(5.0, 5.0, 0.0);
theModelViewMatrix.DumpByColumns(matEntries);
glUniformMatrix4fv(modelviewMatLocation, 1, false, matEntries);
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(restartPrimitiveCode); // Only need to call this once
glDrawElements(GL_TRIANGLE_STRIP, 26, GL_UNSIGNED_INT, 0);
glDisable(GL_PRIMITIVE_RESTART);
The calls to glEnable
and glDisable
enable
and disable the use of primitive restarts; the call to
glPrimitiveRestartIndex
specifies the restart code.
It is not necessary to call these for every rendering, but it is
included here to illustrate how they work.
Remark:
As before, it was not necessary to use a different VAO.
Using the same call to glMultiDrawElements
with primitive restart
will also work with myVAO[vaoDrawElements]
.