Using VBO + VAO in openGL

Recently I've doing a lot of openGL and had some time to read up on the specs regarding VBOs and VAOs. Reading the openGL specs is something one needs to get into as it takes some level of understanding to filter out the necessary parts. In this post I want to share some of my understanding of how Vertex Array Objects, Vertex Buffers and Shaders can work nicely together with each other. 

First I'll show you a basic example of how VBOs, VAOs and shaders are used together. This examples uses openFrameworks and my camera class from my library. It shows the testApp.h and testApp.cpp. I'm importing the vertices from an .obj file.

#pragma once

#include "ofMain.h"
#include 

// Vertex and fragment shader sources
static const char* VS = "" \
  "uniform mat4 u_projection_matrix;" \
  "uniform mat4 u_view_matrix;" \
  "attribute vec4 a_position;" \
  "attribute vec2 a_tex; " \
  "attribute vec3 a_normal; " \
  " " \
  "void main() { " \
  "   gl_Position = u_projection_matrix * u_view_matrix * a_position; " \
  "}" \
  "";
  
static const char* FS = "" \
  "void main() { \n" \
  "  gl_FragColor.rgba = vec4(1.0,0.5,1.0,0.5); " \
  "}" \
  "";

// In this example, We only use the vertex position
struct MyVertex {
  MyVertex(float x, float y, float z) {
    position[0] = x;
    position[1] = y; 
    position[2] = z;
    memset(tex, 0, 2 * sizeof(float));
    memset(normal, 0, 3 * sizeof(float));
  }

  float position[3];
  float tex[2];
  float normal[3];
};

class testApp : public ofBaseApp{
public:
  Camera cam;
  GLuint vao;
  GLuint vbo;
  GLuint vert_shader;
  GLuint frag_shader;
  GLuint prog; 
  int vertex_count;
};

Above I defined a MyVertex structure to which I refer in the text below. Note that I'm only using the positions in my code to keep it simple.

In the code below, I'm using a custom Camera class to get the projection and view matrices. I'm also reading a OBJ file that exported from Blender.

// testApp.cpp
#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup(){
  ofBackground(33);
  cam.perspective(60.0f, 4.0/3.0f, 0.1, 10.0f);
  cam.setPosition(0.0f, 0.0f, 2.5f);

  // Step 1: create shaders
  // ----------------------
  vert_shader = glCreateShader(GL_VERTEX_SHADER);
  frag_shader = glCreateShader(GL_FRAGMENT_SHADER);

  glShaderSource(vert_shader, 1, &VS, NULL);
  glShaderSource(frag_shader, 1, &FS, NULL);

  glCompileShader(vert_shader); 
  glCompileShader(frag_shader);

  prog = glCreateProgram();
  glAttachShader(prog, vert_shader);
  glAttachShader(prog, frag_shader);

  glBindAttribLocation(prog, 0, "a_position");
  glBindAttribLocation(prog, 1, "a_tex");
  glBindAttribLocation(prog, 2, "a_normal");

  glLinkProgram(prog);
  glUseProgram(prog);
  
  // Camera + Projection matrices
  GLint projection_id = glGetUniformLocation(prog, "u_projection_matrix");
  GLint cam_id = glGetUniformLocation(prog, "u_view_matrix");
  glUniformMatrix4fv(projection_id, 1, false, cam.pm().getPtr());
  glUniformMatrix4fv(cam_id, 1, false, cam.vm().getPtr());
          
  // Step 2: Create the vertices buffer (on cpu). 
  // We read the vertices from a .obj file.
  std::vector vertices;
  std::ifstream ifs;
  ifs.open(ofToDataPath("suzanne.obj").c_str(), std::fstream::in);
  if(!ifs.is_open()) {
    ::exit(1);
  }
  std::string line;
  while(ifs >> line) {
    if(line.size() == 1 && line[0] == 'v') {
      float x,y,z = 0.0f;
      ifs >> x >> y >> z;
      MyVertex vertex(x,y,z);;
      vertices.push_back(vertex);
    }
  }
  vertex_count = vertices.size();

  // Step 3: Pass the vertices to openGL and create VBO
  glGenBuffers(1, &vbo);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(
     GL_ARRAY_BUFFER
     ,sizeof(MyVertex) * vertices.size()
     ,vertices[0].position
     ,GL_STATIC_DRAW
  );

  // Step 3: Create VAO which keeps track of the attribute info
  glGenVertexArraysAPPLE(1, &vao); // apple specific here
  glBindVertexArrayAPPLE(vao);

  // Step 4: Create a VAO and set the attribute data
  glEnableVertexAttribArray(0); // a_position
  glEnableVertexAttribArray(1); // a_tex
  glEnableVertexAttribArray(2); // a_normal

  // IMPORTANT: we're "binding" our VBO again.. this triggers the currently bound
  // VAO to use this VBO for the next function calls which influence the attribute
  // state. The state is set by calling glVertexAttribPointer.
  glBindBuffer(GL_ARRAY_BUFFER, vbo); 

  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE
                        ,sizeof(MyVertex), (GLvoid*)offsetof(MyVertex, position));
  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE
                       ,sizeof(MyVertex), (GLvoid*)offsetof(MyVertex, tex));
  glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE
                       ,sizeof(MyVertex), (GLvoid*)offsetof(MyVertex, normal));

  ofEnableAlphaBlending();
}

//--------------------------------------------------------------
void testApp::draw(){
  glBindVertexArrayAPPLE(vao);
  glUseProgram(prog);
  glDrawArrays(GL_POINTS, 0, vertex_count);
}

VBO

Think of a VBO just as an buffer into which you can store bytes. Most ofter these bytes will be information about your 3D model, namely the vertex positions, texture coordinates, colors, normals etc. But with modern openGL, you're not limited to these attributes. You can add any attribute you like. 

Whenever you have any of this kind of data, you can use a VBO. Using an VBO is an efficient way to your 3D model data. When you want to create a new VBO you have to ask openGL to create one for you. Creating a VBO follows the same approach as with many other openGL "creation" functions. First you call a "glGen*" then you call "glBind*". So in this case:

glGenBuffers(1, &my_vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);

As you can see I'm binding the VBO as an GL_ARRAY_BUFFER. So, we could use the name "Array Buffer" instead of VBO. If you want to relate this to your favorite programming language one might say this is a bit similar to:

var array = new Array()

Okay, so now we have a buffer into which we can store some data. As I said above, you probably want to store vertex attributes for your 3D model in a this VBO. But what do I meean with "vertex attributes"? Well good question :-). A vertex is not just a position of a 3D model, although the term vertex is often used as a 3D/2D point. A better mental model of a vertex is a group of data which is related to a point in space. Of course this means that you need an actual 2D or 3D point. A good way to explain this, is by creating a vertex structure (C++)

struct MyVertex {
   float position[3];
   float uv[2]; 
   float normal[3]
};

This would be a typical definition of a vertex with 3 attributes: position, uv and normal. The great thing with using the correct way of openGL, is that you can use any attribute you like. To continue, your 3D model will contain many of these vertices. A basic solution to store these vertices is using a std::vector variable. Once you've filled the vector with all vertices that make up your model you need hand over all the vertices to openGL. For this you use glBufferData:

glBufferData(
   GL_ARRAY_BUFFER
   ,sizeof(MyVertex) * vertices.size()
   ,vertices[0].position
   ,GL_STATIC_DRAW
)

Once you've called glBufferData() the VBO that you've bound previously with glBindBuffer() will contain the all of the vertices from the "std::vector vertices" (I did not add code which filled this vertices vector.) 

To summarize, when I think of a VBO I think about these three functions (there is a bit more into it, but this will do fine):

glGenBuffer(...)
glBindBuffer(...)
glBufferData(...)

How simple is that, right? Now, lets say we have a VBO which contains all the vertices for our 3D model. What do we need more to render this? OpenGL can't do anything with a buffer alone; for openGL, the buffer is just a bunch of bytes. Next step is telling openGL how the data is structured. Before you can tell openGL how it's structured you need to know that a std::vector is just a flat array in memory. So it's a continuous stream of bytes with position, uv, normal, position, uv, normal, position, uv, normal etc.. etc.. To explain this I can start with how shaders and vertex attributes are tight together or how a VAO is related to this. I'll start with the shader part.

SHADER

I'm not going to dive into the workings of a shader, I'll only explain some of the details you need to understand VBO + VAOs. A shader is a tiny program that's executed for each of the vertices and fragments. The first kind of shader is called a Vertex-Shader and as you might guessed is executed for each vertex. The other one is a Fragment Shader, which I won't describe further. So a vertex shader is excuted for each vertex. As I explained above I see a vertex as a group of attributes related to a point.  To use these attributes in a shader, you need to tell the shader what kind of attributes you want to work with. One of the ways to tell this, is to add the attributes in the source like this:

attribute vec4 a_position;
attribute vec2 a_tex;
attribute vec3 a_normal;

void main() {
  // some shader code.
}

In the example bove I created a vertex shader which can with with all of the attributes I defined in my MyVertex structure. Note: I'm using a vec4 for a_position but I defined a float[3] for position in MyVertex. openGL will set the fourth value of a_position to 1.0. The reason I'm using a vec4 is because I will multiply this a_position by a matrix 4x4. 

Ok, awesome, we now have a VBO and a vertex shader. As you might have guessed we still need to tell openGL how the we want to use the VBO and how it's data is organized in memory and how this data is related to attributes. This is probably the most confusion part of Vertex Array Objects (VAOs).

VAO

Ok this is the toughest part of this article and I've been told many different things on the workings of this and I've to use the right words/names to explain this. To be clear, I want to describe my mental model of VAOs. The next paragraphs are not yet completely about VAOs but I think it's good to describe these functions here anyway.

It's good to keep in mind, that openGL still needs to find a way to use the bytes in the VBO for the correct attributes in the shader. For example, we don't want that openGL uses MyVertex.normal for the a_position attribute. 

We can tell this by making connections between the attributes in the shader and the data in the VBO. When we look at the MyVertex struct, and lets say we look into the memory it will look something like:

- 12 bytes for the position
- 8 bytes for the uv
- 12 bytes for the normal

Note that a float takes 4 bytes. When the vertex-shader works with one vertex at a time, we can tell opengl "hey! for a_position, use the first 12 bytes, for the a_tex, the next8 and for a_normal, use the last 12 bytes".  We can tell this by using glVertexAttribPointer().

In 99% of the cases when you're working with VBOs + VAOs and you get an memory access error, it's because you've used wrong values for glVertexAttribPointer. Lets say the a_position from my shader is stored at a index with the number 0, a_tex at 1, and a_normal at 2. Now we have enough information to tell openGL how to connect the dots. In the following code snippet, we are going to tell that the a_norma is related to MyVertex.normal, that the values are floats, they don't need to be normalized and that you can find them at an offset of 20 bytes (see offsetof).

glVertexAttribPointer(
   2
   ,3
   ,GL_FLOAT
   ,GL_FALSE
   ,sizeof(MyVertex)
   ,(GLvoid*)offsetof(MyVertex, normal)
);

After calling glVertexAttribPointer() the result is that openGL knows something about an attribute, the size, the type, the stride, etc. To make this a bit more clear, lets say that openGL has a structure like this:

struct VertexAttribPointerInfo {
  int attribute_index;
  int number_of_elements; 
  DataType type; // e.g. FLOAT
  bool must_normalize; 
  int stride; 
  int offset;
  int vbo;
  bool enabled;
}

So for each of the attributes openGL creates a new VertexAtribPointerInfo instance, sets the correct info and keeps track of these settings in a general array of VertexAttribPointerInfo. (keep in mind, that this is a mental model; how openGL manages the data it totally up to the way the driver is implemented.) One thing to pay attention to, is the "int vbo" in VertexAttribPointer. Each time you call glVertexAttribPointer() you must be sure a VBO is bound. This means that you can bind another VBO per attribute. This could make sense when you e.g. have tons of attributes, but only one attribute changes everytime. Then you want to create a separate VBO and update only this data everytime it changes. 

Lets recap a bit. We now have a way to tell openGL how to relate a VBO, to attributes. I've left a couple of pieces from this puzzle though. Importantly I didn't tell you that you first need to enable a vertex attribute if you want to use it. You can do this by calling glEnableVertexAttribArray(). By calling glEnableVertexAttribArray(), you're setting the VertexAttribPointerInfo.enabled to true. This operation works on the general VertexArribPointerInfo array of opengl or on the currently bound VAO (note, I made this VertexAttribPointerInfo array up to give a modal of what's going on). 

Because we have 3 vertex attributes: position, tex, normal, we need to enable all three of them. You can use more then 3 vertex attribute index numbers. You could for example use indices 2,3,4. But lets say we want to use 0,1,2, then we call:

glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);

OpenGL would do something like this, where vao is the currently bound VAO or the generic vertex attribute array. 

VertexAttribPointerInfo vao[8];
vao[0].enabled = true;
vao[1].enabled = true;
vao[2].enabled = true;

Ok there is one last thing to do before I can explain more about what a VAO is. We did not yet tell openGL what the vertex attributes actually are. Yes, we did use the indices 0,1,2, but openGL has no way of knowing that these indices related to the variables in our shader (a_position, a_tex and a_normal). We use glBindAttribLocation() for this. This involves a bit of info I don't want to add here, but assume we have a program object then we can bind the attributes to the correct indices like this:

glBindBuffer(GL_ARRAY_BUFFER, my_vbo);
glBindAttribLocation(program, 0, "a_position");
glBindAttribLocation(program, 1, "a_tex");
glBindAttribLocation(program, 2, "a_normal");

In newer versions of openGL you can specify the location in the shader, but for this article this is a good solution too, which will also work with older versions the 3. Note that I'm explicitly binding the VBO again (with glBindBuffer(GL_ARRAY_BUFFER, my_vbo)). It depends a bit on how you structure you code, but you need to make sure that you bind the VBO after binding the VAO. When a VAO is bound and you call glBindBuffer, it's notifies the VAO that you want to work with this VBO.

I hope this article gives you a basic understanding of how VBOs and VAOs work. If you have any comments please contact me.