Search code examples
c++openglbufferopengl-3

glBufferData and glBufferSubData Offset


I'm attempting to render Suzanne (from Blender) in OpenGL 3.3, but the buffer data doesn't seem to be correct. I get this:

https://gyazo.com/ab82f9acb6854a49fccc527ed96cc4e8

I also tried to render a sphere with a simple texture:

https://gyazo.com/85c1e87fcc4eab128ca37b1a0cb1deaa

My importer inserts the vertex data into a std::vector as single floats:

if(line.substr(0,2) == "v ")
{
/** Vertex position */
    std::istringstream s(line.substr(2));
    float v[3];
    s >> v[0]; s >> v[1]; s >> v[2];

    this->vertices.push_back(v[0]);
    this->vertices.push_back(v[1]);
    this->vertices.push_back(v[2]);
}

I setup the array buffer as follows:

glGenBuffers(1, &this->vbo);
glBindBuffer(GL_ARRAY_BUFFER, this->vbo);

glBufferData(GL_ARRAY_BUFFER,
    sizeof(float)*(this->vertices.size()+this->textures.size()+this->normals.size()),
    NULL,
    GL_STATIC_DRAW);

And then I insert the actual data using glBufferSubData

glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*this->vertices.size(), this->vertices.data());
glBufferSubData(GL_ARRAY_BUFFER, sizeof(float)*this->vertices.size(), sizeof(float)*this->textures.size(), this->textures.data());
glBufferSubData(GL_ARRAY_BUFFER, sizeof(float)*(this->vertices.size()+this->textures.size()), sizeof(float)*this->normals.size(), this->normals.data());

I also insert the indices in the same way (GL_ELEMENT_ARRAY_BUFFER of course).

I then point to the information:

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (GLvoid*)0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (GLvoid*)(sizeof(float)*this->v.size()));
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (GLvoid*)(sizeof(float)*this->v.size()+this->vt.size()));

My vertex shader takes in the data like this:

layout(location = 0) in vec3 position;
layout(location = 1) in vec2 texCoord;
layout(location = 2) in vec3 normals;

Am I screwing up the offsets?

EDIT: I figured out the biggest issue! I wrote an external Lua program to convert the obj files to a format that was easier to import, but it ended up messing with the data and duplicating the indices on the "f #/#/# #/#/# #/#/#" so the file looked like this (x->y->x) instead of (x->y->z)

Also fixed a few other errors thanks to the responses below!


Solution

  • Without seeing your shaders I can't be 100% sure if your calls to glVertexAttribPointer are legit. I also can't tell if you want interleaved vertex data in a single VBO or not. What you currently have packs all of the vertex positions in first, then all of the texture coordinates, and finally all of the normals.

    To interleave the data you would first want to put all of it into a single array (or vector) so that each vertex repeats the PPPTTNNN pattern. Where PPP are the three position floats, TT are the two texcoord floats, and NNN are the three normal floats.

    It would look something like this (using bogus types, values, and spacing to help illustrate the pattern):

    float[] vertices = {
     /* pX, pY, pZ, tX, tY, nX, nY, nZ */
         1,  1,  1,  0,  0,  1,  1,  1,     // vertex 1
         0,  0,  0,  1,  1,  0,  0,  0,     // vertex 2
         1,  1,  1,  0,  0,  1,  1,  1,     // vertex 3
        ...   
    };
    

    Let's say you put it all into a single vector called vertices, then you can upload it with a single command:

    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * this->vertices.size(), this->vertices.data(), GL_STATIC_DRAW);
    

    You could also put each attribute into its own VBO. How you decide to store the data on the GPU is ultimately up to you. If you are purposely storing the data the way you have it, let me know in the comments and I'll update the answer.

    Ok, now the shader bits.

    Let's say you've got a vertex shader that looks like this:

    in vec3 position;
    in vec2 texcoord;
    in vec3 normal;
    out vec2 uv;
    
    void main() {
        gl_Position = vec4(position, 1);
        uv = texcoord;
    }
    

    And a fragment shader that looks like this:

    in vec2 uv;
    uniform sampler2D image;
    out vec4 color;
    
    void main() {
        color = texture(image, uv);
    }
    

    Then you would want the following glVertexAttribPointer calls:

    int stride = (3 + 2 + 3) * sizeof(float);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)0);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)3);
    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)5);
    

    Notice how the first parameter to each call is a different number. This corresponds to position, texcoord, normal respectively in the vertex shader.

    Also texture coordinates are typically only a pair of floats (e.g. vec2 texcoord) so I changed the second parameter to 2 for the texcoord call.

    Finally the last parameter, when using an interleaved array, only needs to specify the offset per vertex. Thus we get 0, 3, and 5 for position offset, texcoord offset, and normal offset respectively.

    Hopefully this gets you where you want to be.

    Check out the docs.gl page on glVertexAttribPointer for more info.