Introduction
In OpenGL, rendering starts with a call to either:
- glDrawArrays()
- glDrawElements()
Both of these functions will render a scene from data in a currently bound and enabled OpenGL buffer. Let's review the steps for rendering.
The 9 steps for rendering
The steps required for rendering can be summarized as follows:
- Generate (glGenBuffers()): Informs OpenGL to create a Buffer.
- Bind (glBindBuffer()): Informs OpenGL to use this buffer for subsequent operations.
- Buffer Data (glBufferData() or glBufferSubData()): Informs OpenGL to allocate and initialize sufficient memory for the currently bound buffer.
- Get Location of Attributes (glGetAttribLocation()): Get location of attributes in current active shader.
- Get Location of Uniform (glGetUniformLocation()): Get location of Uniforms in currect active shader.
- Enable (glEnableVertexAttribArray()): Enable the attribute locations found in the shader.
- Set Pointers (glVertexAttribPointer()):Informs OpenGL about the types of data in bound buffers and any memory offsets needed to access the data.
- Draw (glDrawArrays() or glDrawElements()): Informs OpenGL to render a scene using data in currently bound and enabled buffers.
- Delete (glDeleteBuffers()): Tell OpenGL to delete previously generated buffers and free associated resources.
First, an OpenGL buffer is created and bound. Once bound, data can be loaded onto the buffer with either glBufferData() or glBufferSubData(). Next, the locations of the attributes and uniforms are obtained by using glGetAttribLocation() and glGetUniformLocation(), respectively. Once the attribute locations are known, the attribute locations are enabled by calling glEnabledVertexAttribArray().
Once the attribute locations are enabled, we can tell OpenGL which data in the bound buffer corresponds to the attributes in the shader. This is done by providing a memory offset to the data in the buffer with glVertexAttribPointer(). And finally, we can start the rendering process by calling glDrawArrays() which will render a screen with data found in the current bound buffer.
How to render with glDrawArrays
How does glDrawArrays() knows what data to render? glDrawArrays() will render an object with data found in the current bound buffer. In listing 1, we bound a buffer after having it created (line 2).
Listing 1. Preparing a buffer for rendering.
//Vertex data of character
float vertexData[36]={1.0,0.4,0.9,1.0,....};
//1. Create a buffer
GLuint myBuffer;
glGenBuffers(1,&myBuffer);
//2. Bind a buffer
glBindBuffer(GL_ARRAY_BUFFER,myBuffer);
//3. Load data in the buffer
glBufferData(GL_ARRAY_BUFFER,sizeof(vertexData),vertexData,GL_STATIC_DRAW);
//4. Get the location of the shader attribute called "position". Assume positionLocation is a global GLuint variable
positionLocation=glGetAttribLocation(programObject, "position");
//5. Get Location of uniforms
modelViewProjectionUniformLocation = glGetUniformLocation(programObject,"modelViewProjectionMatrix");
//6. Enable the attribute location
glEnableVertexAttribArray(positionLocation);
//7. Link the buffer data to the shader attribute locations.
glVertexAttribPointer(positionLocation,3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)0);
//ready to render
Listing 1 sets up a buffer with data ready to render. Ideally, listing 1 is set up once. whereas the code to render is called continuously.
Listing 2 shows a simple rendering example. Notice that before you call glDrawArrays you have to enable the attribute locations (line 1). After the rendering, these locations should be disabled (line 3).
The first parameter in glDrawArrays simply asks how you want to connect the vertices in the buffer data. The vertices can be connected as lines, triangles or points. The second parameter simply aks for the starting index in the data array. The third parameter asks for the total number of indices to be rendered.
Note: in the code above, vertexData contains 36 indices.
Listing 2. Rendering routine.
//1. enable the attribute vertex location
glEnableVertexAttribArray(positionLocation);
//2. Start the rendering process
glDrawArrays(GL_TRIANGLES, 0, 36);
//3. Disable the attribute vertex location
glDisableVertexAttribArray(positionLocation);
Source Code
Source code for "How to render with glDrawArrays".
How to render with glDrawElements
Rendering with glDrawElements() is more efficient than rendering with glDrawArrays(). With glDrawElements() you provide a set of indices that will guide OpenGL through the primitive assembly stage. These set of indices reduce any redundant vertex connection which may occur if using glDrawArrays().
The process of preparing an object for rendering using glDrawElements() is very similar to glDrawArrays(), except that we create a new buffer and bind it to GL_ELEMENT_ARRAY_BUFFER. For example, lines 7-8 in listing 3 shows the creation and binding of the new buffer called elementBuffer. Line 9 shows the loading of the index data found in the array cubeIndex.
Listing 3. Preparing rendering with glDrawElements.
//Vertex data of character
float vertexData[36]={1.0,0.4,0.9,1.0,....};
int cubeIndex[36]={0, 1, 2,
2, 3, 0,
4, 5, 6,
6, 7, 4...};
//1. Create a buffer
GLuint myBuffer;
glGenBuffers(1,&myBuffer);
//2. Bind a buffer
glBindBuffer(GL_ARRAY_BUFFER,myBuffer);
//3. Load data in the buffer
glBufferData(GL_ARRAY_BUFFER,sizeof(vertexData),vertexData,GL_STATIC_DRAW);
//4. Get the location of the shader attribute called "position". Assume positionLocation is a global GLuint variable
positionLocation=glGetAttribLocation(programObject, "position");
//5. Get Location of uniforms
modelViewProjectionUniformLocation = glGetUniformLocation(programObject,"modelViewProjectionMatrix");
//6. Enable the attribute location
glEnableVertexAttribArray(positionLocation);
//7. Link the buffer data to the shader attribute locations.
glVertexAttribPointer(positionLocation,3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)0);
//8. Create a new buffer for the indices
GLuint elementBuffer;
glGenBuffers(1, &elementBuffer);
//9. Bind the new buffer to binding point GL_ELEMENT_ARRAY_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
//10. Load the buffer with the indices found in cubeIndex array
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cubeIndex), cubeIndex, GL_STATIC_DRAW);
//ready to render
Rendering is done as shown in listing 4. Again, we start the rendering loop by first enabling the attribute locations (line 1). glDrawElements() will use the indices as it constructs the object using data in the currently bound buffer. That is, the current buffer bound to GL_ARRAY_BUFFER. Finally, we disable the attribute location (line 3).
Listing 4. Rendering with glDrawElements
//1. enable the attribute vertex location
glEnableVertexAttribArray(positionLocation);
//2. Start the rendering process
glDrawElements(GL_TRIANGLES,sizeof(cubeIndex)/4,GL_UNSIGNED_INT,(void*)0);
//3. Disable the attribute vertex location
glDisableVertexAttribArray(positionLocation);
Source Code
Source code for "How to render with glDrawElements".