Harold Serrano

View Original

Rendering efficiently with OpenGL

Introduction

In the previous post you learnt how to prepare an OpenGL buffer and how to initialize the rendering process. OpenGL is a state machine, thus before it renders a 3D model it needs to reload/unload its rendering states. This reloading/unloading of states can cause a burden on OpenGL. To prevent this, you should use what is known as a Vertex Array Object or (VAO). A VAO provides an efficient way to render a scene and diminish the burden on OpenGL.

The rendering process

Let's review the 9 steps required for rendering.

  1. Generate (glGenBuffers()): Informs OpenGL to create a Buffer.
  2. Bind (glBindBuffer()): Informs OpenGL to use this buffer for subsequent operations.
  3. Buffer Data (glBufferData() or glBufferSubData()): Informs OpenGL to allocate and initialize sufficient memory for the currently bound buffer.
  4. Get Location of Attributes (glGetAttribLocation()): Get location of attributes in current active shader.
  5. Get Location of Uniform (glGetUniformLocation()): Get location of Uniforms in currect active shader.
  6. Enable (glEnableVertexAttribArray()): Enable the attribute locations found in the shader.
  7. Set Pointers (glVertexAttribPointer()):Informs OpenGL about the types of data in bound buffers and any memory offsets needed to access the data.
  8. Draw (glDrawArrays() or glDrawElements()): Informs OpenGL to render a scene using data in currently bound and enabled buffers.
  9. 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 are obtained by using glGetAttribLocation(). Once the locations are known, these locations are enabled by calling glEnabledVertexAttribArray().

Once the locations are enabled, we can tell OpenGL which data in the bound buffer correspond to the attribute in the shader. This is done by providing a memory offset to the data in the buffer with glVertexAttribPointer(). Finally, we can start the rendering process by calling glDrawArrays() which will render a screen with data found in the current bound buffer.

Putting all of this in code:

Listing 1. Preparing 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

The code above sets up a buffer with data ready to render. Ideally, the code above is only set up once. whereas the code to render is called continuously. If you forgot how rendering occurs, please see this post. The code in listing 2 shows a simple rendering example.

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);

Using VAO for efficiency.

Using a Vertex Array Object is quite simple. We create and bind a VAO before the creation of a OpenGL buffer. So, the nine steps required for rendering can be modified as follows:

  1. Generate a VAO (glGenVertexArrays): Informs OpenGL to create a VAO.
  2. Bind the VAO (glBindVertexArray): Informs OpenGL to bind a VAO.
  3. Generate a VBO (glGenBuffers()): Informs OpenGL to create a Buffer.
  4. Bind the VBO (glBindBuffer()): Informs OpenGL to use this buffer for subsequent operations.
  5. Buffer Data (glBufferData() or glBufferSubData()): Informs OpenGL to allocate and initialize sufficient memory for the currently bound buffer.
  6. Get Location of Attributes (glGetAttribLocation()): Get location of attributes in current active shader.
  7. Get Location of Uniform (glGetUniformLocation()): Get location of Uniforms in currect active shader.
  8. Enable (glEnableVertexAttribArray()): Enable the attribute locations found in the shader.
  9. Set Pointers (glVertexAttribPointer()):Informs OpenGL about the types of data in bound buffers and any memory offsets needed to access the data.
  10. Draw (glDrawArrays() or glDrawElements()): Informs OpenGL to render a scene using data in currently bound and enabled buffers.
  11. Delete (glDeleteBuffers()): Tell OpenGL to delete previously generated buffers and free associated resources.

Preparing a buffer with a VAO is simply done as shown in listing 2. Before we create an OpenGL buffer, we create and bind a VAO (lines 1-2)

Listing 2. Preparing for rendering with VAO
//Vertex data of character
float vertexData[36]={1.0,0.4,0.9,1.0,....};

//1. Generate a Vertex Array Object. Assume you have a global GLuint myVertexArrayObject declared.
glGenVertexArraysOES(1,&myVertexArrayObject);

//2. Bind the Vertex Array Object
glBindVertexArrayOES(myVertexArrayObject);

//3. Create a buffer
GLuint myBuffer;

glGenBuffers(1,&myBuffer);

//4. Bind a buffer
glBindBuffer(GL_ARRAY_BUFFER,myBuffer);

//5. Load data in the buffer
glBufferData(GL_ARRAY_BUFFER,sizeof(vertexData),vertexData,GL_STATIC_DRAW);

//6. Get the location of the shader attribute called "position". Assume positionLocation is a global GLuint variable

positionLocation=glGetAttribLocation(programObject, "position");

//7. Get Location of uniforms
modelViewProjectionUniformLocation = glGetUniformLocation(programObject,"modelViewProjectionMatrix");

//8. Enable the attribute location
glEnableVertexAttribArray(positionLocation);

//9. Link the buffer data to the shader attribute locations.
glVertexAttribPointer(positionLocation,3, GL_FLOAT, GL_FALSE, 0, (const GLvoid *)0);

//ready to render

Before calling glDrawArrays(), you simply bind the VAO as shown in listing 3, line 1. Binding the VAO will enable all the attribute locations and any other states set during the rendering setup. Notice that you no longer need to call glEnableVertexAttribArray() before calling glDrawArrays(). This is because the VAO is taking care of enabling all the attributes locations for you. You can now render as many characters on the scene without any burden on OpenGL.

Listing 3. Rendering routine with VAO
//1. Bind the VAO
glBindVertexArrayOES(myVertexArrayObject);

//2. Start the rendering process
glDrawArrays(GL_TRIANGLES, 0, 36);

//3. Unbind the VAO
glBindVertexArrayOES(0);

Source Code

Source code for "Using VAO for efficiency".

Final Result

character rendering