2
\$\begingroup\$

I have been trying out some OpenGL things and was wondering something:

How does OpenGL know which buffer to draw when glDrawElements is called (since there are three)?

Here's a code snippet (from a tutorial):

    // Use our shader
    glUseProgram(programID);

    glm::vec3 lightPos = glm::vec3(4,4,4);
    glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z);
    glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]); // This one doesn't change between objects, so this can be done once for all objects that use "programID"

    glm::mat4 ModelMatrix1 = glm::mat4(1.0);
    glm::mat4 MVP1 = ProjectionMatrix * ViewMatrix * ModelMatrix1;

    // Send our transformation to the currently bound shader, 
    // in the "MVP" uniform
    glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP1[0][0]);
    glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix1[0][0]);


    // Bind our texture in Texture Unit 0
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, Texture);
    // Set our "myTextureSampler" sampler to use Texture Unit 0
    glUniform1i(TextureID, 0);

    // 1rst attribute buffer : vertices
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glVertexAttribPointer(
        0,                  // attribute
        3,                  // size
        GL_FLOAT,           // type
        GL_FALSE,           // normalized?
        0,                  // stride
        (void*)0            // array buffer offset
    );

    // 2nd attribute buffer : UVs
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
    glVertexAttribPointer(
        1,                                // attribute
        2,                                // size
        GL_FLOAT,                         // type
        GL_FALSE,                         // normalized?
        0,                                // stride
        (void*)0                          // array buffer offset
    );

    // 3rd attribute buffer : normals
    glEnableVertexAttribArray(2);
    glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
    glVertexAttribPointer(
        2,                                // attribute
        3,                                // size
        GL_FLOAT,                         // type
        GL_FALSE,                         // normalized?
        0,                                // stride
        (void*)0                          // array buffer offset
    );

    // Index buffer
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);

    // Draw the triangles !
    glDrawElements(
        GL_TRIANGLES,      // mode
        indices.size(),    // count
        GL_UNSIGNED_SHORT,   // type
        (void*)0           // element array buffer offset
    );

I have absolutely no idea how it would possibly know that the first buffer is the one that contains the vertices, and the others non-drawable information like UVs.

Can someone help me with this?

Thanks

\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

How does opengl know which buffer to draw when glDrawElements is called(since there are 3)?

It uses all three; it uses all the buffers you enabled, bound and referenced. They're all "drawable information."

i have absolutely no idea how it would possibly know that the first buffer is the one that contains the vertexes, and the others non drawable information like UV's.

It doesn't. What OpenGL knows (because you tell it, via glVertexAttribPointer's first argument) is that the first buffer is assigned to attribute 0, and the second to attribute 1, and so on.

You also need to ensure these attributes match up with how you use the corresponding variables in your shader (the in-qualified variables). Generally you do this via glBindAttribLocation in your C++ code or by using layout(location = X) in the shader code. If you do neither of those, OpenGL automatically assigns them, which is somewhat brittle.

\$\endgroup\$
6
  • \$\begingroup\$ Thanks! But is okay if i let OpenGL assign them automatically? Or will that cause problems? \$\endgroup\$
    – user110167
    Commented Jan 24, 2018 at 21:22
  • \$\begingroup\$ As noted in that link, GL assigns them arbitrarily at program link time, so you may get unexpected results, particularly when you add a new attribute and now have to manually re-arrange everything to make sure they match up. It's probably okay for quick-and-dirty code, or whatever, but I don't recommend it. The effort involved in manually binding them via the "layout(location)" syntax is trivial and well-worth the peace of mind, in my opinion. \$\endgroup\$
    – user1430
    Commented Jan 24, 2018 at 21:24
  • \$\begingroup\$ Ok, so where would i put that layout(location) in my file? Or how do i use the glBindAttribLocation? \$\endgroup\$
    – user110167
    Commented Jan 24, 2018 at 21:27
  • \$\begingroup\$ "layout(location = X)" goes in front of the variable in the shader, as noted in the link. The docs for glBindAttribLocation are here. \$\endgroup\$
    – user1430
    Commented Jan 24, 2018 at 21:30
  • \$\begingroup\$ I took a look inside teh StandardShading.vertexshader that came with the tutorial and it contains this: layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec2 vertexUV; layout(location = 2) in vec3 vertexNormal_modelspace; does that mean it is all ok? \$\endgroup\$
    – user110167
    Commented Jan 24, 2018 at 21:43
0
\$\begingroup\$

When glVertexAttribPointer is called opengl looks at the buffer bound to GL_ARRAY_BUFFER and associates that buffer to the attribute you are setting.


There is a second method to associate the buffer with the attributes using separate vertex format. (available from opengl 4.3+ or with the associated extension)

constexpr int vertexBindingPoint = 0;
constexpr int uvBindingPoint = 1;
constexpr int normalBindingPoint = 1;// free to choose, must be less than the GL_MAX_VERTEX_ATTRIB_BINDINGS limit

// 1rst attribute buffer : vertices
glEnableVertexAttribArray(0);
glVertexAttribFormat(
    0,                  // attribute
    3,                  // size
    GL_FLOAT,           // type
    GL_FALSE,           // normalized?
    0                   // attr offset
);
glVertexAttribBinding(0, vertexBindingPoint);

// 2nd attribute buffer : UVs
glEnableVertexAttribArray(1);
glVertexAttribFormat(
    1,                                // attribute
    2,                                // size
    GL_FLOAT,                         // type
    GL_FALSE,                         // normalized?
    0                                 // attr offset
);
glVertexAttribBinding(1, uvBindingPoint);

// 3rd attribute buffer : normals
glEnableVertexAttribArray(2);
glVertexAttribFormat(
    2,                                // attribute
    3,                                // size
    GL_FLOAT,                         // type
    GL_FALSE,                         // normalized?
    0                                 // attr offset
);
glVertexAttribBinding(2, normalBindingPoint);
//at this point the VAO is set up 

//this can be done per mesh that uses the same vertex format while reusing the same VAO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
glBindVertexBuffer(vertexBindingPoint, vertexbuffer, 0, 3*sizeof(float));
glBindVertexBuffer(uvBindingPoint, uvbuffer, 0, 2*sizeof(float));
glBindVertexBuffer(normalBindingPoint, normalbuffer, 0, 3*sizeof(float));

// Draw the triangles !
glDrawElements(
    GL_TRIANGLES,      // mode
    indices.size(),    // count
    GL_UNSIGNED_SHORT,   // type
    (void*)0           // element array buffer offset
);   

this has the advantage of letting the driver know that the format doesn't change and it can avoid patching the vertex shader each time you change the vao.

\$\endgroup\$

You must log in to answer this question.