14/14
7. Indexing
Let us consider the case where we want to draw a quadrangle composed of four vertices at position \(p_0=(0,0,0)\), \(p_1=(1,0,0)\), \(p_2=(1,1,0)\), \(p_3=(0,1,0)\). This quadrangle will be composed of two triangles \((p_0,p_1,p_2)\) and \((p_0,p_2,p_3)\).
It is possible to send to the GPU these two triangles as an array of six different 3D coordinates. However, in doing this, we duplicate the coordinates of \(p_0\) and \(p_2\) twice. In addition to using memory space, this doesn’t take into account the fact that vertices \(p_0\) and \(p_1\) correspond in principle to a single point in space. For instance, modifying the coordinates \(p_0\) would thus need to be performed at two place in the buffer. More generally on a mesh with multiple shared vertices between triangles, one should track of all coordinates duplication which would be error prone.
A more efficient way to encode such mesh structure is to separate the coordinate geometry from the connectivity of the mesh. This encoding called indexing is handled by OpenGL. A mesh is then represented by two structure
-
The geometry corresponding to an array of contiguous coordinates (vector of float). Each vertex coordinates only appear once in the array, in any order.
-
The connectivity corresponding to an array of triangle indices (vector of unsigned integer). Each triangle is designated by three consecutive index.
Application to the quadrangle
-
Declaration of the structure geometry/connectivity
// Geometry (vertex position)
const std::vector<GLfloat> position = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.5f, 0.5f, 0.0f,
-0.5f, 0.5f, 0.0f
};
// Connectivity (triangle index)
const std::vector<GLuint> index = {
0, 1, 2,
0, 2, 3
};
-
Send data to GPU
Indices defining the connectivity have to be stored on GPU memory and follow the same procedure than VBO. Pay attention to the fact that the type associated to the VBO storing indices is GL_ELEMENT_ARRAY_BUFFER (and not GL_ARRAY_BUFFER).
// Fill VBO for position
GLuint vbo_position = 0;
glGenBuffers(1, &vbo_position);
glBindBuffer(GL_ARRAY_BUFFER, vbo_position);
glBufferData(GL_ARRAY_BUFFER, position.size()*sizeof(GLfloat), &position[0], GL_STATIC_DRAW );
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Fill VBO for index
glGenBuffers(1, &vbo_index);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_index);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, index.size()*sizeof(GLuint), &index[0], GL_STATIC_DRAW );
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
-
Draw call
Finally, the drawing call is made with the function glDrawElements
// Draw call
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_index);
glDrawElements(GL_TRIANGLES, 3*2, GL_UNSIGNED_INT, nullptr);
-
glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices)
-
mode: Specifies the type of primitive to draw, i.e. how to interpret indices as primitives. OpenGL is able to render different Primitives. In our case, GL_TRIANGLE interpret each triplet of indices as a specific triangle (most generic way to define indices on a mesh). Other interpretation such as STRIP or FAN can be used in case where the set of indices are following a specific structure and can save memory and lead to faster rendering. Finally segments (called line in OpenGL) can also be rendered.
-
count: the total number of vertex to be rendered, in our case we need to render 6 vertices in total (3 vertex per triangle times 2 triangles).
-
type: The type used to store indices, unsigned integer in our case.
-
indices: Offset (stored as pointer) indicating the first index to be read, in our case the index starts at 0.
-
-
Compile and execute the corresponding code

-
Add an extra triangle to model the following figure
