There are two ways to use glDrawElements. Let's illustrate with a simple example. Consider the cube shown below. Note that its xmax face is on the right; its ymax face is on top, and its zmax face is towards the front. Suppose we wish to draw its 6 faces. We create a VBO with the eight vertices whose order in the VBO is as indicated in the figure.
Clearly the zmax, xmax, and zmin faces can be rendered, in that order, as:
glBindVertexArray(theVAO); // zmax face: glVertexAttrib3f(shaderIF->pvaLoc("mcNormal"), 0.0, 0.0, 1.0); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // xmax face: glVertexAttrib3f(shaderIF->pvaLoc("mcNormal"), 1.0, 0.0, 0.0); glDrawArrays(GL_TRIANGLE_STRIP, 2, 4); // zmin face: glVertexAttrib3f(shaderIF->pvaLoc("mcNormal"), 0.0, 0.0, -1.0); glDrawArrays(GL_TRIANGLE_STRIP, 4, 4);
The remaining three faces must be done differently since the vertices are not in the order they would need to be in order to draw those faces with glDrawArrays. This is where glDrawElements comes in.
The xmin face needs to use vertices {6, 7, 0, 1}. The ymin face needs to use vertices {6, 0, 4, 2}. The ymax face needs to use vertices {1, 7, 3, 5}. Assuming the VAO is still bound (e.g., we pick up right where the rendering of the first three faces ended), the xmin face can be rendered as:
// xmin face: GLuint xminIndices[] = {6, 7, 0, 1}; glVertexAttrib3f(shaderIF->pvaLoc("mcNormal"), -1.0, 0.0, 0.0); glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, xminIndices);
The code for the ymin and ymax faces is analogous and is left as an exercise. Note that – like glDrawArrays – if there are multiple VBOs associated with the VAO, then the index list passed to glDrawElements is used when extracting per-vertex data from each VBO.
The code shown above represents the simplest way to use glDrawElements, and it should be perfectly adequate for anything done in this course. Nevertheless, it is important to note that the index list itself is sent to the GPU every time glDrawElements is called. For small to moderately-sized models, this is not a big deal. However, if the index lists are very long and/or a moderately large index list is referenced thousands of times or more during each display callback, you may start to see some performance degradation. This leads to an alternative approach: place the index lists into so-called "element buffers" that, like the VBOs we have seen to date, reside permanently on the GPU.
If we were to draw the final three faces of our block in that way, we would do something more like the following:
GLuint indexList[3][4] = { { 6, 7, 0, 1 }, // xmin face { 6, 0, 4, 2 }, // ymin face { 1, 7, 3, 5 } // ymax face }; glGenBuffers(3, ebo); // ebo is assumed to be appropriately declared as an instance variable for (int i=0 ; i<3 ; i++) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[i]); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4*sizeof(GLuint), indexList[i], GL_STATIC_DRAW); }GL_ELEMENT_ARRAY_BUFFERS are different than the GL_ARRAY_BUFFERS we have seen to date. Among other things, this means we use neither glVertexAttribPointer nor glEnableVertexAttribArray with element buffers.
// xmin face glVertexAttrib3f(shaderIF->pvaLoc("mcNormal"), -1.0, 0.0, 0.0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo[0]); glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, nullptr);
Final Important Notes:
Consider drawing spheres, Bezier surfaces, and other similar doubly-curved surfaces. The most common way to draw them is to create adjacent rows of points sampled along the surface and render the surface as a series of GL_TRIANGLE_STRIP primitives, using corresponding points in each pair of adjacent rows. Using only glDrawArrays, interior rows of points would have to be stored twice (make sure you understand why!), hence the total number of points that must be stored in VBOs is roughly twice the number required. This can lead to significant wasted storage on the GPU.
Using glDrawElements (typically with element buffers) allows you to create and store in VBOs the data for each point in the piecewise linear approximation exactly once. Of course the cost is that you need to store the element buffer, but the storage for the element buffer will be much less than would be required to store the data for interior points twice since each requires at least 6 floating point numbers per vertex: (x, y, z) & (nx, ny, nz). For example, if we were drawing the Bezier surface on the right using a 50 x 50 grid of points and only storing coordinates and normals per-vertex, using only glDrawArrays would require 117,600 bytes, whereas using element buffers and glDrawElements would require only 79,600 bytes – 38 KB (32.3%) less GPU storage.