Harold Serrano

View Original

Loading data into OpenGL Buffers

Introduction

Data from a character such as vertices, normals and UV coordinates are loaded into OpenGL objects called Buffer Objects. There are many ways to load these data. In this section, You will learn all the possible ways that these data can be loaded.

Simple data loading

First, let's load a simple array of data representing the vertex position of a character. We are going to create a single OpenGL buffer, bind it to GL_ARRAY_BUFFER and place the vertex data in it. Once the data has been loaded, we will link it to the shader attribute position.

Listing 1. Simple data loading
//Vertex data of character
float vertexData[250]={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"

GLuint positionLocation=glGetAttribLocation(programObject, "position");

//5. Get Location of uniforms called "modelViewProjectionMatrix"

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

The first thing we do before loading data is to create a buffer myBuffer as shown in line 1 listing 1. The buffer is bound to the binding point GL_ARRAY_BUFFER (line 2). Once binding is complete, we are able to load the data into myBuffer. This is accomplished by using the OpenGL function glBufferData as shown in line 3.

The OpenGL function glBufferData requires the binding point of myBuffer, the size of the data to be loaded and the pointer to the data vertexData. The parameter GL_STATIC_DRAW states that the data will not change.

For the data to properly be used in the shader, we need to get the location of the attribute position which is referenced in the shader (line 4). This location is then enabled as shown in line 6. Once enabled, we can link the attribute position with the data in the vertexData array. The link is establish by using the function glVertexAttribPointer as shown in line 7. This function requires the location of the attribute, the number of components per vertex (in our case it is 3. x, y and z components), type of data, normalization, stride and a pointer to the first component of the data.

Loading Interleaved data

The above example shows how to load a simple array of data representing the position of a 3D model. However, most likely you will also need to load data representing normals and uv-coordinates. These data may be interleaved as shown below in the characterData array. The first two components of the array represent the uv coordinates. The next three represent normal coordinates and the last three represent vertex position.

Listing 2. Loading interleaved data
GLfloat characterData[192] =
    {
        // Data layout for each line below is:
        // UV1,UV2, Normal1, Normal2, Normal3, positionX, positionY, positionZ
        0.00000, 1.00000, 0.00000, 0.00000, 1.00000, -0.50000, -0.50000, 0.50000,
        0.00000, 0.00000, 0.00000, 0.00000, 1.00000, -0.50000, 0.50000, 0.50000...};

//1. Create a Vertex Buffer Object

GLuint myBuffer;

glGenBuffers(1, &myBuffer);

//2. Bind the Vertex Buffer Object

glBindBuffer(GL_ARRAY_BUFFER, myBuffer);

//3. Dump the data into the Buffer

glBufferData(GL_ARRAY_BUFFER, sizeof(characterData), characterData, GL_STATIC_DRAW);

//4. Get the location of the shader attribute called "position"

GLuint positionLocation=glGetAttribLocation(shaderProgram, "position");

//5. Get the location of the shader attribute called "normal"

GLuint normalLocation=glGetAttribLocation(shaderProgram, "normal");

//6. Get the location of the shader attribute called "texCoord"

GLuint textureLocation=glGetAttribLocation(shaderProgram, "texCoord");

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

//8. Enable attribute locations

glEnableVertexAttribArray(positionLocation);

glEnableVertexAttribArray(normalLocation);

glEnableVertexAttribArray(textureLocation);

//9. Link the buffer data to the shader attribute locations

glVertexAttribPointer(textureLocation, 2, GL_FLOAT, GL_FALSE, 32, BUFFER_OFFSET(0));

glVertexAttribPointer(normalLocation, 3, GL_FLOAT, GL_FALSE, 32, BUFFER_OFFSET(8));

glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 32, BUFFER_OFFSET(20));

The process is essentially the same as shown in listing 1. Lines 1-3 simply create an OpenGL buffer and loads data into the buffer. Lines 4, 5 and 6 query for the attribute locations position, normal and textCoord, respectively. Line 8 enables each of these attribute location.

Line 9 links the data in the characterData array with the attribute locations. The last parameter of the function glVertexAttribPointer asks for a pointer offset to the components in the array.

For example, The uv-coordinates have an offset of 0, since it is the first component in the array. The offset for the components representing the normal coordinates is sizeof(float)*(elementPosition)=sizeof(float)*2=8. The vertex position pointer is set to 20. sizeof(float)*(elementPosition)=sizeof(float)*5=20.

The stride is set to 32, because that is when the attributes repeat in the array.

Source Code

Source code for "Loading Interleaved data".

Loading using glBufferSubData

You may also have vertex, normal and uv data in different arrays. These data can still be loaded into the same OpenGL buffer by using the function glBufferSubData.

The process is the same. We create a OpenGL buffer and bind it as shown in listing 3, lines 1&2. However, the parameters in glBufferData differ as shown in line 3. The size of the buffer is the sum of the size of all three arrays and the parameter pointer for the data is set to NULL (line 3).

Listing 3. Loading using glBufferSubData
float position[250]={1.0,0.0,1.0,
                   0.0,1.0,0.0,
                   0.5,0.5,1.0,
                   0.5,0.0,0.4,....};

float normal[250]={1.0,0.0,1.0,
                   0.0,1.0,0.0,
                   0.5,0.5,1.0,
                   0.5,0.0,0.4,....};

float uv[250]={1.0,0.0,
               1.0,0.0,
               1.0,0.0,
               0.5,0.5,....};     

//1. Create a buffer
GLuint myBuffer;

glGenBuffers(1, &myBuffer);

//2. Bind the Vertex Buffer Object

glBindBuffer(GL_ARRAY_BUFFER, myBuffer);

//3. Dump the data into the Buffer. Notice the NULL set as a pointer to the data.

glBufferData(GL_ARRAY_BUFFER, sizeof(position)+sizeof(normal)+sizeof(uv), NULL, GL_STATIC_DRAW);

//3a. Load position data using glBufferSubData

glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(position), position);

//3b. Load normal data using glBufferSubData
glBufferSubData(GL_ARRAY_BUFFER, sizeof(position), sizeof(normal), normal);

//3c. Load UV data using glBufferSubData
glBufferSubData(GL_ARRAY_BUFFER, sizeof(position)+sizeof(normal), sizeof(uv), uv);

//4. Get the location of the attributes from the shader
GLuint positionLocation=glGetAttribLocation(shaderProgram,"position");
GLuint normalLocation=glGetAttribLocation(shaderProgram,"normal");
GLuint uvLocation=glGetAttribLocation(shaderProgram, "texCoord");

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

//6. enable the attribute locations
glEnableVertexAttribArray(positionLocation);
glEnableVertexAttribArray(normalLocation);
glEnableVertexAttribArray(uvLocation);

//7. Link the buffer data to the shader attribute locations

//7a. Link buffer data to position attribute location   
glVertexAttribPointer(positionLocation,3,GL_FLOAT,GL_FALSE,0,(const GLvoid*)(0));

//7b. Link buffer data to normal attribute location  
glVertexAttribPointer(normalLocation,3,GL_FLOAT,GL_FALSE,0,(const GLvoid*)sizeof(position));

//7c. Link buffer data to texture attribute location
glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)sizeof(position)+sizeof(normal));

Each data is then loaded separately using glBufferSubData as shown in line 3a. Notice the second parameter of the function glBufferSubData representing the offset to the data. It increases as you load more data (lines 3b-3c).

After that, everything is pretty much the same until you get to the function glVertexAttribPointer (line 7). As you link buffer data to the attributes locations, the last parameter of glVertexAttribPointer increases by the array size of the previous linked location (lines 7a-7c).

I like to load data using glBufferSubData, however, you can load the data using either method without any performance penalty.

Source Code

Source code for "Loading using glBufferSubData".

Final Result

character rendering