Understanding VAO's/VBO's and drawing two objects.

I’m new to OpenGL 4. All the tutorials I’ve seen draw one object (usually a triangle) in the beginning. I’m having trouble understanding how VAO’s and VBO’s relate to an object. I want to draw two different objects: a spaceship (a triangle) and an asteroid(an octagon). I’ve figured out a way and was wondering if this is the correct way as I go further in my OpenGL adventures. I created one VAO and two VBO’s (asteroid_buffer_object and ship_buffer_object). Then inside InitializeVertexBuffer() I’m generating the two buffer objects and binding them. Then in display() I’m again binding the asteroid_buffer_object, drawing it, and then binding ship_buffer_object and drawing it. So I’m wondering if this is correct or am I doing something redundant?



#include <algorithm>
#include <string>
#include <vector>
#include <cstdio>
#include <iostream>
#include <cmath>
#include <GL/glew.h>
#include <GL/freeglut.h>

using namespace std;

GLuint CreateShader(GLenum eShaderType, const std::string &strShaderFile)
{
  GLuint shader = glCreateShader(eShaderType);
  const char *strFileData = strShaderFile.c_str();
  glShaderSource(shader, 1, &strFileData, NULL);

  glCompileShader(shader);

  GLint status;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
  if (status == GL_FALSE)
  {
    GLint infoLogLength;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);

    GLchar *strInfoLog = new GLchar[infoLogLength + 1];
    glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);

    const char *strShaderType = NULL;
    switch(eShaderType)
    {
    case GL_VERTEX_SHADER: strShaderType = "vertex"; break;
    case GL_GEOMETRY_SHADER: strShaderType = "geometry"; break;
    case GL_FRAGMENT_SHADER: strShaderType = "fragment"; break;
    }

    fprintf(stderr, "Compile failure in %s shader:
%s
", strShaderType, strInfoLog);
    delete[] strInfoLog;
  }

  return shader;
}

GLuint CreateProgram(const std::vector<GLuint> &shaderList)
{
  GLuint program = glCreateProgram();

  for(size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
    glAttachShader(program, shaderList[iLoop]);

  glLinkProgram(program);

  GLint status;
  glGetProgramiv (program, GL_LINK_STATUS, &status);
  if (status == GL_FALSE)
  {
    GLint infoLogLength;
    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);

    GLchar *strInfoLog = new GLchar[infoLogLength + 1];
    glGetProgramInfoLog(program, infoLogLength, NULL, strInfoLog);
    fprintf(stderr, "Linker failure: %s
", strInfoLog);
    delete[] strInfoLog;
  }

  for(size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
    glDetachShader(program, shaderList[iLoop]);

  return program;
}

GLuint theProgram;

const std::string strVertexShader(
  "#version 330
"
  "layout(location = 0) in vec4 position;
"
  "void main()
"
  "{
"
  "   gl_Position = position;
"
  "}
"
);

const std::string strFragmentShader(
  "#version 330
"
  "out vec4 outputColor;
"
  "void main()
"
  "{
"
  "   outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
"
  "}
"
);

void InitializeProgram()
{
  std::vector<GLuint> shaderList;

  shaderList.push_back(CreateShader(GL_VERTEX_SHADER, strVertexShader));
  shaderList.push_back(CreateShader(GL_FRAGMENT_SHADER, strFragmentShader));

  theProgram = CreateProgram(shaderList);

  std::for_each(shaderList.begin(), shaderList.end(), glDeleteShader);
}

const int num_asteroid_vertices = 8;
const int num_ship_vertices = 3;
GLfloat asteroid_vertices[num_asteroid_vertices][2];

void set_asteroid_vertices()
{
  GLfloat angle = 0.0;
  GLfloat r = 1.0;
  for(int i=0; i < num_asteroid_vertices; i++)
  {
    angle = i * 2*M_PI/num_asteroid_vertices;
    asteroid_vertices[i][0] = r*cos(angle);
    asteroid_vertices[i][1] = r*sin(angle);
    cout << r*cos(angle) << ", ";
    cout << r*sin(angle) << endl;
  }
}

GLfloat ship_vertices[][2] = {
  { -0.05f, -0.05f },
  {  0.05f, -0.05f },
  {  0.00f,  0.05f }
};

GLuint asteroid_buffer_object;
GLuint ship_buffer_object;
GLuint vao;


void InitializeVertexBuffer()
{
  glGenBuffers(1, &asteroid_buffer_object);
  glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
  glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid_vertices), asteroid_vertices, GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER, 0);

  glGenBuffers(1, &ship_buffer_object);
  glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
  glBufferData(GL_ARRAY_BUFFER, sizeof(ship_vertices), ship_vertices, GL_STATIC_DRAW);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
 
}

//Called after the window and OpenGL are initialized. Called exactly once, before the main loop.
void init()
{
  set_asteroid_vertices();
  InitializeProgram();
  InitializeVertexBuffer();

  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);
}

//Called to update the display.
//You should call glutSwapBuffers after all of your rendering to display what you rendered.
//If you need continuous updates of the screen, call glutPostRedisplay() at the end of the function.
void display()
{
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  glUseProgram(theProgram);

  glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

  glDrawArrays(GL_LINE_LOOP, 0, num_asteroid_vertices);

  glDisableVertexAttribArray(0);
  glUseProgram(0);

  glUseProgram(theProgram);

  glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
  glEnableVertexAttribArray(0);
  glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

  glDrawArrays(GL_LINE_LOOP, 0, num_ship_vertices);

  glDisableVertexAttribArray(0);  

  glutSwapBuffers();
}

//Called whenever the window is resized. The new window size is given, in pixels.
//This is an opportunity to call glViewport or glScissor to keep up with the change in size.
void reshape (int w, int h)
{
  glViewport(0, 0, (GLsizei) w, (GLsizei) h);
}

int main(int argc, char** argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA);
  glutInitWindowSize(1000, 1000);
  glutInitContextVersion(4, 3);
  glutInitContextProfile(GLUT_CORE_PROFILE);
  glutCreateWindow(argv[0]);

  if (glewInit()) {
    cerr << "Unable to initialize GLEW ... exiting" << endl;
    exit(EXIT_FAILURE);
  }

  init();

  glutDisplayFunc(display);

  glutMainLoop();
}

You’re not making use of VAOs. You create one VAO at start-up and leave it bound thereafter. This doesn’t really provide you with any advantages over not using a VAO.

The basic way you use VAOs is e.g.:

Initialisation:


    glGenVertexArrays(1, &asteroid_vao);
    glBindVertexArray(asteroid_vao);
    glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glGenVertexArrays(1, &ship_vao);
    glBindVertexArray(ship_vao);
    glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glBindVertexArray(0);

Rendering:


    glUseProgram(theProgram);
    glBindVertexArray(asteroid_vao);
    glDrawArrays(GL_LINE_LOOP, 0, num_asteroid_vertices);
    glBindVertexArray(ship_vao);
    glDrawArrays(GL_LINE_LOOP, 0, num_ship_vertices);
    glBindVertexArray(0);
    glUseProgram(0);

A VAO stores the state set by the glBindBuffer(GL_ARRAY_BUFFER), glEnableVertexAttribArray, and glVertexAttribPointer calls (as well as a few others, e.g. glVertexAttribDivisor and glBindBuffer(GL_ELEMENT_ARRAY_BUFFER)). So you can use a single glBindVertexArray call instead of many individual calls. If you have multiple attributes, this becomes significant.

However: if the number of objects is large (much larger than one ship and a few asteroids), it’s important to draw “batches” rather than individual objects. Typically, you wouldn’t have a separate attribute array (or group of arrays) for each “object”. You’d have one set of arrays containing the data for many objects which could conceivably be rendered in a single draw call.

If you have many objects in one set of arrays, you can still draw a subset of them by varying the offset/start/count/etc parameters in the draw calls (and/or using draw calls with more parameters, e.g. glMultiDrawElements, glDrawElementsIndirect, etc). But if the objects are spread across multiple sets of attribute arrays, you’re forced to use multiple draw calls.

Why aren’t these 2 called, like they are in the Red Book:

glGenBuffers(1, &asteroid_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid_vertices), asteroid_vertices, GL_STATIC_DRAW);

[QUOTE=michaelglaz;1262775]Why aren’t these 2 called, like they are in the Red Book:

glGenBuffers(1, &asteroid_buffer_object);
glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid_vertices), asteroid_vertices, GL_STATIC_DRAW);[/QUOTE]

Your code already has those, so I didn’t duplicate it. The code to initialise the VAO should be added to the end of InitializeVertexBuffer(). It assumes that the VBOs already exist and contain data.

Can you show me where these two lines go exactly?

This is one way to do it:


void InitializeVertexBuffer()
{
    glGenBuffers(1, &asteroid_buffer_object);
    glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
    glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid_vertices), asteroid_vertices, GL_STATIC_DRAW);
    glGenVertexArrays(1, &asteroid_vao);
    glBindVertexArray(asteroid_vao);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
 
    glGenBuffers(1, &ship_buffer_object);
    glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
    glBufferData(GL_ARRAY_BUFFER, sizeof(ship_vertices), ship_vertices, GL_STATIC_DRAW);
    glGenVertexArrays(1, &ship_vao);
    glBindVertexArray(ship_vao);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
 
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0); 
}

One more question. How do I draw ten asteroids? I have this so far:


struct asteroid
{
  GLfloat asteroid_vertices[num_asteroid_vertices][2];  
};

void set_asteroid_vertices()
{
  GLfloat angle = 0.0;
  GLfloat r = 0.25;

  srand(time(NULL));

  for(vector<asteroid>::size_type i=0; i != num_asteroids; i++)
  {
    asteroid a;
 
    float x = (1 + rand() % 200 / 100.0) - 2;
    float y = (1 + rand() % 200 / 100.0) - 2;

    for(int j=0; j < num_asteroid_vertices; j++)
    {
      angle = j * 2*M_PI/num_asteroid_vertices;

      a.asteroid_vertices[j][0] = r*cos(angle) + x;
      a.asteroid_vertices[j][1] = r*sin(angle) + y;
    }
    asteroids.push_back(a);
  }
}

void display()
{
...
glDrawArrays(GL_LINE_LOOP, 0, num_asteroid_vertices*asteroids.size());
...
}


But I don’t know how to have the line loop end at the 8th vertex.

One option is to use GL_LINES, using indexed drawing (glDrawElements()) so you don’t have to duplicate each vertex. The index array would look like

{0,1, 1,2, 2,3, 3,4, 4,5, 5,6, 6,7, 7,0, 8,9, 9,10, …}

The other options require a relatively recent version of OpenGL.

The first is primitive restart (glEnable(GL_PRIMITIVE_RESTART) and glPrimitiveRestartIndex()). This also requires indexed rendering but can be used with GL_LINE_LOOP. Assuming the primitive restart index has been set to 9999, the index array would look like e.g.

[0,1,2,3,4,5,6,7,9999,8,9,10,…]

Essentially, the primitive restart index marks the boundary between primitives, enabling multiple loops, strips or fans to be drawn in a single call.

Another option is instanced rendering, i.e. glDrawArraysInstanced(). But for something as simple as asteroids, that’s a bit like using a sledgehammer to crack a nut.

Essentially, you’d have two attribute arrays: one containing num_asteroids positions (which are the origin of each asteroid), and one containing num_asteroid_vertices vertices (which are the asteroid vertices relative to its origin). A vertex shader would add the two together to generate the actual vertex coordinates.

The main disadvantage is the added complexity. The main advantages are the memory saving (you only need num_asteroids + num_asteroid_vertices coordinate pairs, rather than num_asteroids * num_asteroid_vertices) and the fact that you only need to update one position to move an asteroid rather than needing to update all of its vertices.

I have the latest version of OpenGL. Which method would you recommend? I don’t care if it’s a sledgehammer method - I’m just interested in learning what will be useful for me down the road.

Then try all of them.

The first two approaches (lines and primitive restart) could be done fairly quickly, instanced rendering requires learning how to create and use shaders (which you’ll want to do sooner or later).

So I added the following code to create 2 asteroids:


vector<GLuint> asteroid_indices {0,1, 1,2, 2,3, 3,4, 4,5, 5,6, 6,7, 7,0, 8,9, 9,10, 10,11, 11,12, 12,13, 13,14, 14,15, 8 };

GLuint IndexBufferId;



void InitializeVertexBuffer()
{
    glGenBuffers(1, &IndexBufferId);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferId);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(asteroid_indices), &asteroid_indices[0], GL_STATIC_DRAW);

   ...
}


void display()
{
   ...

  glDrawElements(GL_LINES, 16, GL_UNSIGNED_INT, 0);

  ...
}

But this doesn’t seem to work and the red book is not very specific on this.

The end of the array should be “14,15, 15,8};”, i.e. the 15 should appear twice.

sizeof(asteroid_indices) will return the compile-time size of the std::vector structure, which doesn’t include the size of the contained data (which is in a dynamically-allocated array referenced from the structure). You need e.g.


    asteroid_indices.size() * sizeof(asteroid_indices[0])

The sizeof(…) part could be sizeof(GLuint), but using an expression is robust against changes to the type of asteroid_indices.

The second argument to glDrawElements() is the number of indices (32 in this case), not the number of primitives (lines). I suggest using asteroid_indices.size() here.

Hmm, the asteroids aren’t being rendered. Here’s my InitializeVertexBuffer() and display() function:


    void InitializeVertexBuffer()
    {
        glGenBuffers(1, &IndexBufferId);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferId);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, asteroid_indices.size() * sizeof(asteroid_indices[0]), &asteroid_indices[0], GL_STATIC_DRAW);

        glGenBuffers(1, &asteroid_buffer_object);
        glBindBuffer(GL_ARRAY_BUFFER, asteroid_buffer_object);
        glBufferData(GL_ARRAY_BUFFER, sizeof(asteroid)*asteroids.size(), &asteroids[0], GL_STATIC_DRAW);
        cout << sizeof(asteroid) << endl;
        cout << asteroids.size() << endl;
        cout << sizeof(asteroid)*asteroids.size() << endl;
        glGenVertexArrays(1, &asteroid_vao);
        glBindVertexArray(asteroid_vao);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
     
        glGenBuffers(1, &ship_buffer_object);
        glBindBuffer(GL_ARRAY_BUFFER, ship_buffer_object);
        glBufferData(GL_ARRAY_BUFFER, sizeof(ship_vertices), ship_vertices, GL_STATIC_DRAW);
        glGenVertexArrays(1, &ship_vao);
        glBindVertexArray(ship_vao);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
     
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0); 
    }


void display()
    {
      glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
      glClear(GL_COLOR_BUFFER_BIT);

      glUseProgram(theProgram);
      glBindVertexArray(asteroid_vao);
      glBindVertexArray(IndexBufferId);

      glDrawElements(GL_LINES, asteroid_indices.size(), GL_UNSIGNED_INT, 0);

      glBindVertexArray(ship_vao);
      glDrawArrays(GL_LINE_LOOP, 0, num_ship_vertices);

      glBindVertexArray(0);
      glUseProgram(0);

      glutSwapBuffers();
    }


The second call should be to glBindBuffer(), not glBindVertexArray(), i.e.


      glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IndexBufferId);

[QUOTE=GClements;1262883]Then try all of them.

The first two approaches (lines and primitive restart) could be done fairly quickly, instanced rendering requires learning how to create and use shaders (which you’ll want to do sooner or later).[/QUOTE]

I used GL_LINES with indexed drawing (glDrawElements) but I notice that this approach and the second one you mention (primitive restart (glEnable(GL_PRIMITIVE_RESTART) and glPrimitiveRestartIndex) require an array of indices. I’m going to have 10 asteroids on the screen at any one time with 8 vertices each. Wouldn’t that require me to write out an array of 80 index values? That seems like a way too laborious of a process.

So I was wondering if you could give me an example of how glDrawArraysInstanced is used?

Wouldn’t that require me to write out an array of 80 index values?

If it’s the same object drawn 10 times, why not… just do that? Draw each one, with different transform parameters. If you increase the number into the tens of thousands, then you can try instancing to improve performance.

The amount of data involved is negligible. And it sufficed until instanced rendering was added in OpenGL 3.1.

A rough outline is: you have an array containing the vertex positions for one asteroid (relative to the asteroid’s origin), and another array containing the position of each asteroid. The latter would have a divisor (glVertexAttribDivisor) of 1. The vertex shader would have two attributes, which it would add together to obtain the actual vertex position.

The data would be rendered with e.g.


// initialisation
glBindVertexArray(asteroid_vao);
glBindBuffer(GL_ARRAY_BUFFER, asteroid_vertices);
glVertexAttribPointer(0,  2,  GL_FLOAT,  GL_FALSE,  0,  0);
glVertexAttribDivisor(0,  0);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, asteroid_positions);
glVertexAttribPointer(1,  2,  GL_FLOAT,  GL_FALSE,  0,  0);
glVertexAttribDivisor(1,  1);
glEnableVertexAttribArray(1);

// rendering
glBindVertexArray(asteroid_vao);
glDrawArraysInstanced(GL_LINE_LOOP, 0, num_vertices_per_asteroid, num_asteroids);

The vertex shader would look like


layout(location=0) in vec2 vertex;
layout(location=1) in vec2 position;

void main()
{
    gl_Position = vec4(vertex + position, 0, 1);
}

-glEnableVertexAttribArray(0) |1
-glEnableVertexAttribArray(1) |1
-glEnableVertexAttribArray(2) |1
-glEnableVertexAttribArray(3) |1
-glBindVertexArray() |1
–glBindBuffer() |2
—glVertexAttribPointer(0,…) |3
----glVertexAttribDivisor(0,x) |4
—glVertexAttribPointer(2,…) |3
–glBindBuffer() |2
—glVertexAttribPointer(1,…) |3
----glVertexAttribDivisor(1,y) |4
—glVertexAttribPointer(3,…) |3
-glBindVertexArray(0) |1
-glBindBuffer(0) |1
-glDisableVertexAttribArray(0) |1
-glDisableVertexAttribArray(1) |1
-glDisableVertexAttribArray(2) |1
-glDisableVertexAttribArray(3) |1

VAO remembers attributes and attributes remember which buffer to read and how. Just remember to call glBindVertexArray(0) before unbinding anything else so the enabled states are saved. (Only buffer which is actually saved to the VAO instead of being referenced by vertex attributes is GL_ELEMENT_BUFFER, just call glBindBuffer(elementBufferID) and you are good with that).
Enable as many vertex attributes as your VAO needs. Your shader might have more vertex attributes in use than your VAO uses/has but you don’t have to care about matching all the shader attributes. Just bind them to right locations. Vertex data to vertex attribute, color data to color attribute etc.

After that you just
-glUseProgram
–glUniform*()
-glBindVertexArray(x)
–glDraw*()
Rinse and repeat VAO binding and call glDraw*() commands or switch program and do everthing from the beginning (you can use Uniform Buffers to lower or eliminate the use of glUniform*() calls. If multiple VAOs use the same pattern then you should combine those VAOs into 1 and use offsets within the buffer to draw the right shapes. glDrawBaseVertex() is the draw command to use. Then you can just bind a single VAO and just call glDrawBaseVertex() multiple times with different offsets to draw the meshes.

PS: Just check what OpenGL version you are aiming at and use the available options.

Whereas before my asteroids were stored in a vector of float[2], I have now introduced an asteroid class. It looks like this:


class asteroid
{
  std::pair<float, float> vertex;

  std:vector<std::pair<float,float>> vertices;
}

where vertex is the location of the asteroid and vertices holds it’s 8 vertices. Then I have an vector of asteroids. How do I draw the asteroids from this asteroids vector containing objects of class asteroid (specifically I think I need to know the offset and stride I think).

Offset and stride? If your intent is to use buffer objects for this data, then you have to actually store them in buffer objects. That means that your mesh data doesn’t live in C++ data structures; it lives in GPU-accessible memory allocated and managed by buffer objects. Your C++ data structures would have some reference to where their data is, so that it can be rendered. But it wouldn’t own that memory.