VBOs and elements issue

Hey all,

I’m trying to optimize my program to a relatively modern openGL standard. In that interest, I’m trying to write a class for 3D objects with a model and texture loading function (with SOIL) as well as a display function for the VBOs generated in the loader functions. However, nothing ever shows up on the screen. I think I might be mixing something up with the element array.

My object loader code is:

bool MainWindow::VBO::loadOBJFile(const char* file)
{
    std::vector<GLfloat> temp_coords, v_idx, n_idx, uv_idx, final_vertices, final_normals, final_uvs;
    std::vector<glm::vec3> temp_vertices, temp_normals, middle_vertices, middle_normals;
    std::vector<glm::vec2> temp_uv, middle_uvs;
    char buffer[512];
    FILE *Data = NULL;
    int bufferError,
        readError = 3;
    errno_t test;
    test = fopen_s(&Data, file, "r");
    if(Data == NULL)
    {
        qDebug() << "OBJ file" << file << "not found!";
        return false;
    }
    else
    {
        qDebug() << "OBJ file" << file << "loading now into" << this;
    }
    do
    {
        bufferError = fscanf(Data, "%s", buffer);

        if(bufferError == EOF)
        {
            break;
        }

        if(strcmp(buffer, "v") == 0)
        {
            glm::vec3 vertex;
            readError   = fscanf(Data, "%f %f %f
", &vertex.x, &vertex.y, &vertex.z);
            temp_coords.push_back(vertex.x);
            temp_coords.push_back(vertex.y);
            temp_coords.push_back(vertex.z);
            temp_vertices.push_back(vertex);
        }
        else if(strcmp(buffer, "vt") == 0)
        {
            glm::vec2 uv;
            readError   = fscanf(Data, "%f %f
", &uv.x, &uv.y);
            temp_uv.push_back(uv);
        }
        else if(strcmp(buffer, "vn") == 0)
        {
            glm::vec3 normal;
            readError   = fscanf(Data, "%f %f %f
", &normal.x, &normal.y, &normal.z);
            temp_normals.push_back(normal);
        }
        else if(strcmp(buffer, "f") == 0)
        {
            unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
            int matches = fscanf(Data, "%d/%d/%d %d/%d/%d %d/%d/%d
", &vertexIndex[0], &uvIndex[0], &normalIndex[0],
                                                                          &vertexIndex[1], &uvIndex[1], &normalIndex[1],
                                                                          &vertexIndex[2], &uvIndex[2], &normalIndex[2] );
            if (matches != 9){
                qDebug() << "File can't be read by our simple parser! Try exporting with other options";
                qDebug() << "matches is" << matches;
                return false;
            }
            v_idx.push_back(vertexIndex[0]);
            v_idx.push_back(vertexIndex[1]);
            v_idx.push_back(vertexIndex[2]);
            uv_idx.push_back(uvIndex[0]);
            uv_idx.push_back(uvIndex[1]);
            uv_idx.push_back(uvIndex[2]);
            n_idx.push_back(normalIndex[0]);
            n_idx.push_back(normalIndex[1]);
            n_idx.push_back(normalIndex[2]);
        }
        else if(strcmp(buffer, "o") == 0)
        {
            readError = fscanf(Data, "%s", modelName);
            qDebug() << "name of the model is" << modelName;
        }
        else
        {
//            qDebug() << "testString is" << buffer << "and won't be recognized";
        }
    }while(1);
    fclose(Data);

    for( unsigned int i=0; i<v_idx.size(); i++ ){
        unsigned int vertexIndex = v_idx[i];
        glm::vec3 vertex = temp_vertices[ vertexIndex-1 ];
        middle_vertices.push_back(vertex);
    }
    for( unsigned int i=0; i<uv_idx.size(); i++ ){
        unsigned int uvIndex = uv_idx[i];
        glm::vec2 uv = temp_uv[ uvIndex-1 ];
        middle_uvs.push_back(uv);
    }
    for( unsigned int i=0; i<n_idx.size(); i++ ){
        unsigned int normalIndex = n_idx[i];
        glm::vec3 normal = temp_normals[ normalIndex-1 ];
        middle_normals.push_back(normal);
    }

    for(unsigned int i= 0; i<middle_vertices.size();i++)
    {
        final_vertices.push_back(middle_vertices.at(i).x);
        final_vertices.push_back(middle_vertices.at(i).y);
        final_vertices.push_back(middle_vertices.at(i).z);
    }
    for(unsigned int i= 0; i<middle_normals.size();i++)
    {
        final_normals.push_back(middle_normals.at(i).x);
        final_normals.push_back(middle_normals.at(i).y);
        final_normals.push_back(middle_normals.at(i).z);
    }

    for(unsigned int i= 0; i<middle_uvs.size();i++)
    {
        final_uvs.push_back(middle_uvs.at(i).x);
        final_uvs.push_back(middle_uvs.at(i).y);
    }

    glGenBuffers(1, &vertices);
    glBindBuffer(GL_ARRAY_BUFFER, vertices);
    glBufferData(GL_ARRAY_BUFFER, final_vertices.size(), final_vertices.data(), GL_STATIC_DRAW);

    glGenBuffers(1, &normals);
    glBindBuffer(GL_ARRAY_BUFFER, normals);
    glBufferData(GL_ARRAY_BUFFER, final_normals.size(), final_normals.data(), GL_STATIC_DRAW);

    glGenBuffers(1, &uvs);
    glBindBuffer(GL_ARRAY_BUFFER, uvs);
    glBufferData(GL_ARRAY_BUFFER, final_uvs.size(), final_uvs.data(), GL_STATIC_DRAW);

    for( unsigned int i=0; i<v_idx.size(); i++ ){
        v_idx[i]--;
    }

    glGenBuffers(1, &elements);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elements);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, v_idx.size(), v_idx.data(), GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    return true;
}

Texture loader is:

bool MainWindow::VBO::loadTexture(const char* texfile)
{

    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);

    // inspired by http://open.gl/textures
    int width, height;
    unsigned char* image =
            SOIL_load_image(texfile, &width, &height, 0, SOIL_LOAD_RGB);
    if(image == 0)
    {
        qDebug() << "Texture file" << texfile << "not found!";
        return false;
    }
    else
    {
        qDebug() << "image =" << image << "from file" << texfile;
        qDebug() << "width and height =" << width << height;
    }
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
    SOIL_free_image_data(image);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    return true;
}

The display function:


    glm::mat4 anim = glm::yawPitchRoll(-handPitch, -handRoll, -handYaw);
    glm::mat4 model = glm::translate(glm::mat4(1.0f), glm::vec3(handX, handY, handZ));
    glm::mat4 view = glm::lookAt(glm::vec3(xPosTranslate, 0.0, 5.0), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));
    glm::mat4 projection = glm::perspective(45.0f, /*1.0*/0.5f*this->width()/this->height(), 0.1f, 100.0f);
    glm::mat4 mvp = projection * view * model * anim;
    glUseProgram(program);
    glUniformMatrix4fv(uniform_mvp, 1, GL_FALSE, glm::value_ptr(mvp));

    glUseProgram(program);

    glEnableVertexAttribArray(attribute_coord3d);
void MainWindow::drawOBJ(VBO object)
{
    glEnableVertexAttribArray(attribute_coord3d);
    glBindBuffer(GL_ARRAY_BUFFER, object.vertices);
    glVertexAttribPointer(
                attribute_coord3d, // attribute
                3, // number of elements per vertex, here (x,y,z)
                GL_FLOAT, // the type of each element
                GL_FALSE, // take our values as-is
                0, // no extra data between each position
                0 // offset of first element
                );

    glEnableVertexAttribArray(attribute_v_normal);
    glBindBuffer(GL_ARRAY_BUFFER, object.normals);
    glVertexAttribPointer(
                attribute_v_normal, // attribute
                3, // number of elements per vertex, here (x,y,z)
                GL_FLOAT, // the type of each element
                GL_FALSE, // take our values as-is
                0, // no extra data between each position
                0 // offset of first element
                );

    glEnableVertexAttribArray(attribute_v_uv);
    glBindBuffer(GL_ARRAY_BUFFER, object.uvs);
    glVertexAttribPointer(
                attribute_v_uv, // attribute
                2, // number of elements per vertex, here (U,V)
                GL_FLOAT, // the type of each element
                GL_FALSE, // take our values as-is
                0, // no extra data between each position
                0 // offset of first element
                );

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object.elements);
}
int size;
    glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &size);
    glDrawElements(GL_TRIANGLES, size/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);

    glDisableVertexAttribArray(attribute_coord3d);
    glDisableVertexAttribArray(attribute_v_color);
    glDisableVertexAttribArray(attribute_v_uv);
    glDisableVertexAttribArray(attribute_v_normal);
    glUseProgram(0);

I don’t see where my error is, however I’m pretty certain, as I wrote, that something is not quite right about the handling of the elements. I haev a whole bunch of glGetError() calls in between (deleted those here for readability), they return no errors. If any of you pros around here might give the code a quick look, it’d be appreciated. :slight_smile:

replace this code lines:

    glGenBuffers(1, &vertices);
    glBindBuffer(GL_ARRAY_BUFFER, vertices);
    glBufferData(GL_ARRAY_BUFFER, final_vertices.size(), final_vertices.data(), GL_STATIC_DRAW);
 
    glGenBuffers(1, &normals);
    glBindBuffer(GL_ARRAY_BUFFER, normals);
    glBufferData(GL_ARRAY_BUFFER, final_normals.size(), final_normals.data(), GL_STATIC_DRAW);
 
    glGenBuffers(1, &uvs);
    glBindBuffer(GL_ARRAY_BUFFER, uvs);
    glBufferData(GL_ARRAY_BUFFER, final_uvs.size(), final_uvs.data(), GL_STATIC_DRAW);

..blah blah

    glGenBuffers(1, &elements);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elements);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, v_idx.size(), v_idx.data(), GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

with this other code lines:

    glGenBuffers(1, &vertices);
    glBindBuffer(GL_ARRAY_BUFFER, vertices);
    glBufferData(GL_ARRAY_BUFFER, final_vertices.size() * sizeof(GLfloat), &final_vertices[0], GL_STATIC_DRAW);
 
    glGenBuffers(1, &normals);
    glBindBuffer(GL_ARRAY_BUFFER, normals);
    glBufferData(GL_ARRAY_BUFFER, final_normals.size() * sizeof(GLfloat), &final_normals[0], GL_STATIC_DRAW);
 
    glGenBuffers(1, &uvs);
    glBindBuffer(GL_ARRAY_BUFFER, uvs);
    glBufferData(GL_ARRAY_BUFFER, final_uvs.size() * sizeof(GLfloat), &final_uvs[0], GL_STATIC_DRAW);

..blah blah

    glGenBuffers(1, &elements);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elements);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, v_idx.size() *sizeof(GLuint), &v_idx[0], GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

then in this piece of code:


    glDrawElements(GL_TRIANGLES, size/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);

do like that to avoid other passages


    glDrawElements(GL_TRIANGLES, v_idx.size(), GL_UNSIGNED_SHORT, 0);   

it should work, let me know what the result is, :);

Hey thanks I’ll try it!
From my understanding though. shouldn’t it be “final_vertices.size() / sizeof(GLfloat)” instead of “final_vertices.size() * sizeof(GLfloat)”? Alas, if your proposal doesn’t work I’ll try mine. :wink:

edit: Well, neither your nor my way worked, I get the same results. :confused:

Where is the VAO? A non-zero VAO must be bound to do all the configurations of vertex attributes.
?

Do I really need a VAO? I never used them before, and many of the tutorials I’ve seen online don’t mention them.

edit: I don’t think I do, do I? I’ve gotten my code to the point now where the models show on screen, just the faces don’t seem to be quite right.

On the left is the model in Blender (I used a snake texture because it has more irregularities than human skin) and on the right is the output in the program.
As for the red box, it’s the same problem - there seems to be a corner missing.

edit2:
For sake of completion, here is my current code for getting the vertices and elements (I’m omitting the code for normals and UVs here to keep it short, but it’s basically the same) and drawing them:


//variables
std::vector<GLfloat> final_vertices;
std::vector<GLuint> v_idx;
std::vector<glm::vec3> temp_vertices, middle_vertices;

//reading directly from OBJ
if(strcmp(buffer, "v") == 0)
        {
            glm::vec3 vertex;
            readError   = fscanf(Data, "%f %f %f
", &vertex.x, &vertex.y, &vertex.z);
            temp_vertices.push_back(vertex);
        }

//getting the order from the faces
else if(strcmp(buffer, "f") == 0)
        {
            unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
            int matches = fscanf(Data, "%d/%d/%d %d/%d/%d %d/%d/%d
", &vertexIndex[0], &uvIndex[0], &normalIndex[0],
                                                                          &vertexIndex[1], &uvIndex[1], &normalIndex[1],
                                                                          &vertexIndex[2], &uvIndex[2], &normalIndex[2] );
            v_idx.push_back(vertexIndex[0]);
            v_idx.push_back(vertexIndex[1]);
            v_idx.push_back(vertexIndex[2]);
        }

// putting the vertices in the right order into middle_vertices
for( unsigned int i=0; i<v_idx.size(); i++ ){
        unsigned int vertexIndex = v_idx[i];
        glm::vec3 vertex = temp_vertices[ vertexIndex-1 ];
        middle_vertices.push_back(vertex);
    }

//getting the vertices from vector::glm::vec3 to vector::GLfloat
for(unsigned int i= 0; i<middle_vertices.size();i++)
    {
        final_vertices.push_back(middle_vertices.at(i).x);
        final_vertices.push_back(middle_vertices.at(i).y);
        final_vertices.push_back(middle_vertices.at(i).z);
    }

//buffering them
glGenBuffers(1, &vertices);
glBindBuffer(GL_ARRAY_BUFFER, vertices);
glBufferData(GL_ARRAY_BUFFER, final_vertices.size() * sizeof(GLfloat), final_vertices.data(), GL_STATIC_DRAW);

//reducing the indices by 1
for( unsigned int i=0; i<v_idx.size(); i++ ){
    v_idx[i]--;
}

//buffering the elements
glGenBuffers(1, &elements);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elements);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, v_idx.size() * sizeof(GLuint), v_idx.data()/*&v_idx[0]*/, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

//in drawing loop
glEnableVertexAttribArray(attribute_coord3d);
    glBindBuffer(GL_ARRAY_BUFFER, object.vertices);
    glVertexAttribPointer(
                attribute_coord3d, // attribute
                3, // number of elements per vertex, here (x,y,z)
                GL_FLOAT, // the type of each element
                GL_FALSE, // take our values as-is
                0, // no extra data between each position
                0 // offset of first element
                );

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object.elements);
    glDrawElements(GL_TRIANGLES, object.sizeOf_v_idx, GL_UNSIGNED_INT, 0);

You do need a valid non-zero VAO to be bound as the specs of core OpenGL says. In a compatibility mode - not sure. But better to do so.
I believe your problem actually comes from reading the obj file. Inspect it in text editor. Obj may have different number of normals, texcoords and vertices. Normally, you have to load all three into separate arrays, then using the faces’ indices unpack and group all three attributes into a single interleaved array (VBO) tesselating polygons into triangles, then gen and bind VAO, map the VBO using glVertexAttribPointer & glEnableVertexAttribArray, and then draw arrays using GL_TRIANGLES. When you feel a superman enough, think about packing the triangles into something more optimal and use indexed drawing.
Good luck! :slight_smile:

In a compatibility context you can pretend VAOs never existed.

It’s worth noting that glEnableVertexAttribArray, glBindBuffer, glVertexAttribPointer and friends existed in OpenGL before VAOs did (in versions 1.5 and 2.0, to be precise). You can also check the GL_VERSION that an extension is written against, and that it requires: if either predates VAOs then you can safely use it in compatibility or downlevel contexts without VAOs too.

For example: GL_ARB_direct_state_access is specified as requiring GL 2.0, so you can code to GL 2.0 but use that extension if available and without VAOs for it’s buffer object and vertex specification calls.

I’m having a problem understanding why the error might be in the obj file. I just exported it from Blender, when I re-import it it looks just fine.
The file does have a different number of data sets for vertices, tex coords and normals, however that’s why I’m sorting them in my function. After I do that, the std::vectors storing the data are the same length (though the uv vector is only 2/3 of the other’s length).
From what I understand, I don’t explicitly have to put all the vertex, uv and normal data into one big VBO, I can just as well keep them in different ones and send them to the vertex shader by themselves.
However, I tried doing so, but the code crashes :frowning: more specifically, the for loop crashes and I got no idea why.

    glGenBuffers(1, &single_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, single_vbo);
    for(unsigned int i=0; i<final_vertices.size()-2; i++)
    {
        bigBuffer.push_back(final_vertices.at(i));
        bigBuffer.push_back(final_vertices.at(i+1));
        bigBuffer.push_back(final_vertices.at(i+2));
        bigBuffer.push_back(final_uvs.at(i));
        bigBuffer.push_back(final_uvs.at(i+1));
        bigBuffer.push_back(final_normals.at(i));
        bigBuffer.push_back(final_normals.at(i+1));
        bigBuffer.push_back(final_normals.at(i+2));
    }
    glBufferData(GL_ARRAY_BUFFER, bigBuffer.size() * sizeof(GLfloat), bigBuffer.data(), GL_STATIC_DRAW);

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    glVertexAttribPointer(shaderAttributes[0], 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), 0);
    glVertexAttribPointer(shaderAttributes[1], 2, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), (GLvoid*)12);
    glVertexAttribPointer(shaderAttributes[2], 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (GLvoid*)20);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glEnableVertexAttribArray(0);
    glBindVertexArray(0);

edit: I put a

glDrawArrays(GL_TRIANGLES, 0, object.sizeOf_v_idx);

call into my display function instead of the

glDrawElements(GL_TRIANGLES, size/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);

call. Now it works (aside from some culling issues).

o first thing when you do you for loop here there is at least one big mistake:


    for(unsigned int i=0; i<final_vertices.size()-2; i++) // here is you first big one mistake
    {
        bigBuffer.push_back(final_vertices.at(i));  // first cycle: i = 0 so vertices.at(0) //second cycle: i = 1 so vertices.at(1)
        bigBuffer.push_back(final_vertices.at(i+1)); // first cycle: i = 0 so vertices.at(0+1) //second cycle: i = 1 so vertices.at(1 + 1)
        bigBuffer.push_back(final_vertices.at(i+2)); // first cycle: i = 0 so vertices.at(0+2); //second cycle: i = 1 so vertices.at(1 + 2)
        bigBuffer.push_back(final_uvs.at(i)); // like above
        bigBuffer.push_back(final_uvs.at(i+1)); // like above
        bigBuffer.push_back(final_normals.at(i)); // like above
        bigBuffer.push_back(final_normals.at(i+1)); // like above
        bigBuffer.push_back(final_normals.at(i+2)); // like above
    }

how you ca see by the comment i put in your code you are picking the same vertices again and again, i leave you the easy fun part: find how to solve it (really, is not that complicated)

also i think that your program crashes at for loop because probably your final_uvs, final_normals and final_vertex have not the same size so, since you are looping until “i” < final_vertices.size() -2 (why final_vertices.size() -2?) after some loops “i” becomes bigger than the size of one or more of your vectors and more precisely of your final_uvs and/or final_normals, sorry but you have to find another way. good job

EDIT: a solution could be


for(blah blah; buffer_size < vertices_size + uvs_size + normals_size; blah_blah)
{
...
blah bla
...
}

In obj files numeration starts at 1, not 0.
And here:
glVertexAttribPointer(shaderAttributes[0], 3, GL_FLOAT, GL_FALSE, 5sizeof(GLfloat), 0);
glVertexAttribPointer(shaderAttributes[1], 2, GL_FLOAT, GL_FALSE, 6
sizeof(GLfloat), (GLvoid*)12);
glVertexAttribPointer(shaderAttributes[2], 3, GL_FLOAT, GL_FALSE, 5sizeof(GLfloat), (GLvoid)20);
You specify different stride for each attrib pointer, that is wrong. Aside from that, the VBO must be bound BEFORE the glVertexAttribPointer called, because the mapping is performed for the buffer currently bound.
Simply saying, your code is just full of bugs. How can we help you?
By giving an advice to put more attention to what you are writing? Or read the docs for the functions you are using? :slight_smile: