Search code examples
c++copenglvbovao

VAOs and VBOs for rendering different objects


I wrote this "Model" class to load .obj files and allocate data for them in a VBO. Its code is something like this: (notice how it doesn't use VAOs)

class Model {...}

void Model::LoadOBJ(const char *file)
{
    //load vertices, normals, UVs, and put them all in _vec, which is a private data member of std::vector<glm::vec3>
    ...

    //if an .obj file is loaded for the first time, generate a buffer object and bind it
    if(glIsBuffer(_vbo) == GL_FALSE)
    {
        glGenBuffers(1, &_vbo);//_vbo is a private data member
        glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    }
    
    //load the data in the array buffer object
    glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec3) * _vec.size(), &_vec[0][0], GL_STATIC_DRAW);
}

void Model::Draw()
{
    glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    glDrawArrays(GL_TRIANGLES, 0, _numVertices);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

I used to think that the next code would work fine for rendering two different objects:

void init()
{
    //vao dummy (?)
    GLuint VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    //load 3d models
    Model cube = load("cube.obj");
    Model cow = load("cow.obj");

    //the next two lines should be valid for both objects?
    glVertexAttribPointer(prog.GetAttribLocation("vertex"), 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(prog.GetAttribLocation("vertex"));
}

void render()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //draw cube:
    //some matrix transformations
    ...
    cube.Draw();

    //draw cow:
    //some matrix transformations
    ...
    cow.Draw()

    glutSwapBuffers();
}

but it turns out that OpenGL will just draw two cows or two cubes. (depends on which model I load last in init()) two cows two cubes

By the way, I'm pretty sure that in the first image, opengl did try to draw two cows, but the glDrawArrays() function was called with the amount of vertices needed for a cube.

So what am I missing? Do I need a different VAO for every buffer object or something like that?


Solution

  • "Current" state is to blame here, particularly in relation to glVertexAttribPointer.

    All calls to glVertexAttribPointer (...) establish memory pointers relative to the Buffer Object currently bound to GL_ARRAY_BUFFER. We tend to call a Buffer Object that is bound to that location a Vertex Buffer Object, but in reality a single Buffer Object can be used for multiple purposes and they do not actually have a type.

    Think of it sort of like this in modern OpenGL:

    GLuint GL_ARRAY_BUFFER_BINDING = 0; // Only 1 or 2 commands in GL care about this state
    GLuint GL_VERTEX_ARRAY_BINDING = 0; // 0 is an invalid VAO (if this is 0, most vertex commands will generate `GL_INVALID_OPERATION`).
    
    // Generic GPU-side Memory Store
    struct GLBufferObject {
      GLsizeiptr* gpu_base_addr;
    } *gl_buffer_objects;
    
    // Vertex Array State
    struct GLVertexArrayObject {
      GLsizeiptr* attribute_pointers [GL_MAX_VERTEX_ATTRIBUTES];
      GLboolean   attribute_enabled  [GL_MAX_VERTEX_ATTRIBUTES];
      GLuint      GL_ELEMENT_ARRAY_BUFFER_BINDING;
    } *gl_array_objects;
    
    void glBindVertexArray (GLuint array)
    {
      GL_VERTEX_ARRAY_BINDING = array;
    }
    
    void glBindBuffer (GLenum target, GLuint buffer)
    {
      if (target == GL_ARRAY_BUFFER)
        GL_ARRAY_BUFFER_BINDING = buffer;
    }
    
    void glVertexAttribPointer (GLuint index, ..., const GLvoid* offset)
    {
      GLBufferObject*      current_vbo = &gl_buffer_objects [GL_ARRAY_BUFFER_BINDING];
      GLVertexArrayObject* current_vao = &gl_array_objects  [GL_VERTEX_ARRAY_BINDING];
    
      current_vao->attribute_pointers [index] = current_vbo->gpu_base_addr + offset;
    }
    

    The point of this pseudo-code is to show you that there is only one command in OpenGL where what you have bound to GL_ARRAY_BUFFER matters in your entire code: glVertexAttribPointer (...).

    All other commands such as glDrawArrays (...) actually use the state stored in "current_vao" as shown in the mock implementation of glVertexAttribPointer (...).


    In the end, your non-use of VAOs is actually an issue. You are overwriting the attribute pointer for the only VAO that your software currently uses with whichever model was loaded last.

    Review which states GLVetexArrayObject stores in the pseudo-code, and then consider refactoring your own code to take advantage of that. Otherwise, you are going to have to make at least one call to glVertexAttribPointer every time you call void Model::Draw().

    For a less interesting explanation, see this related answer.