Tutorial 7: Using Vertex Buffer Objects
This tutorial is very small compared with previous one. It only will explain what are the Vertex Buffer Objects (VBO) and how to use them. We will part from the tutorial 6. Remove the particle class files and all references to particlesystem in the render.cpp file, and you will be ready to begin with this tutorial.
What are VBO? Well, until now, we’ve sent the data from main memory to OpenGL using vertex arrays. This means that at every frame, all data is transported (copied) from main memory to client memory, but think about textures. They are uploaded only one time, and when they are needed, they are binded and referenced from client memory. VBO does exactly the same thing, but with vertices, instead of texels information. The needed steps to create and use a VBO are very close to the steps needed to create and use a texture. Lets see how.
Mesh class is modified to accommodate the VBO handlers by this way:
class Mesh
{
public:
Mesh(const char *filename, bool useVBO = false);
~Mesh();
void Draw();
bool GetState () {return m_state;};
private:
FixedMesh m_mesh;
bool m_state;
//new VBO variables
bool m_useVBO; //to know if this mesh will use VBO or not
//VBO handlers
GLuint m_indexBuffer;
GLuint m_vertexBuffer;
GLuint m_normalBuffer;
GLuint m_texCoordBuffer;
};
Next, there will be write the mesh.cpp class, which contains all relative information about VBO
Render.cpp
#include "mesh.h"
Mesh::Mesh(const char *filename,bool useVBO)
{
m_state = true;
m_useVBO = useVBO;
m_mesh.Geometry = NULL;
m_mesh.Indices = NULL;
m_mesh.Normals = NULL;
m_mesh.TexCoord = NULL;
GSDHeader header;
FILE *meshFile = fopen(filename,"rb");
if(!meshFile)
{
m_state = false;
return;
}
fread(&header,sizeof(GSDHeader),1,meshFile);
if(header.numberOfSubObjects < 1)
{
m_state = false;
fclose(meshFile);
return;
}
GenericObjectData o;
fread(o.Name,sizeof(char)*128,1,meshFile);
fread(o.ParentName,sizeof(char)*128,1,meshFile);
fread(&o.iC,sizeof(unsigned long),1,meshFile);
fread(&o.vC,sizeof(unsigned long),1,meshFile);
o.Indices = new unsigned int[o.iC];
m_mesh.Indices = new GLushort[o.iC];
fread(o.Indices,sizeof(unsigned int) * o.iC,1,meshFile);
o.Geometry = new float[o.vC * 3];
m_mesh.Geometry = new GLfixed[o.vC * 3];
fread(o.Geometry,o.vC * 3 * sizeof(float),1,meshFile);
o.TexCoord = new float[o.vC * 2];
m_mesh.TexCoord = new GLfixed[o.vC * 2];
fread(o.TexCoord,o.vC * 2 * sizeof(float),1,meshFile);
o.Normals= new float[o.vC * 3];
m_mesh.Normals = new GLfixed[o.vC * 3];
fread(o.Normals,o.vC * 3* sizeof(float),1,meshFile);
for(unsigned int i=0;i<o.vC * 3;i++)
{
m_mesh.Geometry[i]= FixedFromFloat(o.Geometry[i]);
m_mesh.Normals[i] = FixedFromFloat(o.Normals[i]);
}
for(i=0;i<o.vC * 2;i++)
m_mesh.TexCoord[i] = FixedFromFloat(o.TexCoord[i]);
for(i=0;i<o.iC;i++)
m_mesh.Indices[i] = (GLushort)o.Indices[i];
m_mesh.indexCounter = (GLushort)o.iC;
m_mesh.vertexCounter= (GLushort)o.vC;
delete [] o.Indices;
delete [] o.Geometry;
delete [] o.Normals;
delete [] o.TexCoord;
fclose(meshFile);
/*----------------------- NEW CODE -------------------------*/
//if we will use the VBO features, then lets create them
if(m_useVBO)
{
// First, we need create a buffer handle
glGenBuffers(1, &m_indexBuffer);
//Then we have to bind it, like a texture
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
/*Now we have to specify the data. Because this is a index buffer,
we use GL_ELEMENT_ARRAY_BUFFER , in other case, we will use
GL_ARRAY_BUFFER. The buffer size (in bytes) is the next parameter,
Then the buffer itself is specified, and at last, GL_STATIC_DRAW
is a hint that only points to the future usage of this buffer*/
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
m_mesh.indexCounter * sizeof(GLushort),
m_mesh.Indices, GL_STATIC_DRAW);
//remember unbind the VBO if you wint use it in the future
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glGenBuffers(1, &m_vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
glBufferData(GL_ARRAY_BUFFER,
m_mesh.vertexCounter * 3 * sizeof(GLfixed),
m_mesh.Geometry, GL_STATIC_DRAW);
glGenBuffers(1, &m_normalBuffer);
glBindBuffer(GL_ARRAY_BUFFER, m_normalBuffer);
glBufferData(GL_ARRAY_BUFFER,
m_mesh.vertexCounter * 3 * sizeof(GLfixed),
m_mesh.Normals, GL_STATIC_DRAW);
glGenBuffers(1, &m_texCoordBuffer);
glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer);
glBufferData(GL_ARRAY_BUFFER,
m_mesh.vertexCounter * 2 * sizeof(GLfixed),
m_mesh.TexCoord, GL_STATIC_DRAW);
//remember unbind the VBO if you wint use it in the future
glBindBuffer(GL_ARRAY_BUFFER, 0);
/*because VBO data is stored in client memory, we do not need it
in main memory for more time*/
if(m_mesh.Geometry) delete [] m_mesh.Geometry;
if(m_mesh.Indices) delete [] m_mesh.Indices;
if(m_mesh.Normals) delete [] m_mesh.Normals;
if(m_mesh.TexCoord) delete [] m_mesh.TexCoord;
}
}
//--------------------------------------------------------------------
Mesh::~Mesh()
{
if(m_useVBO)
{
glDeleteBuffers(1, &m_indexBuffer);
glDeleteBuffers(1, &m_vertexBuffer);
glDeleteBuffers(1, &m_normalBuffer);
glDeleteBuffers(1, &m_texCoordBuffer);
}
else
{
if(m_mesh.Geometry) delete [] m_mesh.Geometry;
if(m_mesh.Indices) delete [] m_mesh.Indices;
if(m_mesh.Normals) delete [] m_mesh.Normals;
if(m_mesh.TexCoord) delete [] m_mesh.TexCoord;
}
}
//--------------------------------------------------------------------
void Mesh::Draw()
{
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
if(m_useVBO)
{
/*Binding the VBO and assigning NULL to the pointer, means that
next drawing call will use the previus data stored at the VBO*/
glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
glVertexPointer(3, GL_FIXED, 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, m_normalBuffer);
glNormalPointer(GL_FIXED, 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, m_texCoordBuffer);
glTexCoordPointer(2,GL_FIXED, 0, NULL);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
glDrawElements(GL_TRIANGLES,m_mesh.indexCounter,GL_UNSIGNED_SHORT,
NULL);
//VBO unbinds
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
else
{
glVertexPointer(3, GL_FIXED, 0, m_mesh.Geometry);
glNormalPointer(GL_FIXED, 0, m_mesh.Normals);
glTexCoordPointer(2,GL_FIXED, 0, m_mesh.TexCoord);
glDrawElements(GL_TRIANGLES,m_mesh.indexCounter,GL_UNSIGNED_SHORT,
m_mesh.Indices);
}
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
Well, that’s all. Very easy, isn’t? In hardware accelerated systems, VBO can give a great performance boost, but in current PDA’s, without 3D acceleration, the gain won’t be too big, but VBO are a too important topic to do not take care about it.