Advertisement

Clarification on how OpenGL draws entities

Started by January 18, 2017 09:15 AM
3 comments, last by Satharis 7 years, 11 months ago

Not exactly sure how OpenGL draws individual geometric meshes with glDrawArray and glDrawElement. When you call glDrawArray, how exactly does OpenGL know which geometric mesh you are referring to? I used to think it was through VAO, but now I'm not sure.

As shown in the attached image, I have two cubes, one acting as a lamp and another acting like a regular entity in a scene. I'm hazy as to how one draws each individual entity, I know the programmer would have to write the glDrawArray command, but does OpenGL know which one I'm referring to.

Initialization:




    GLfloat vertices[] = {

        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

         0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,

        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,



        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,

         0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,

         0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,

         0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,

        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,

        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,



        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,

        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,

        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,

        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,

        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,



         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

         0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,

         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,

         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,

         0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,

         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,



        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

         0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,

         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,

         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,



        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,

         0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,

         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,

         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,

        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f

    };



    GLuint VBO1, containerVAO;

    glGenVertexArrays(1, &containerVAO);

    glGenBuffers(1, &VBO1);



    glBindBuffer(GL_ARRAY_BUFFER, VBO1);

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);



   glBindVertexArray(containerVAO);

    // Position attribute

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);

    glEnableVertexAttribArray(0);

    // Normal attribute

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));

    glEnableVertexAttribArray(1);







    GLuint VBO2;

    GLuint lampVAO;

    glGenVertexArrays(1, &lampVAO);

   glBindVertexArray(lampVAO);





        glGenBuffers(1, &VBO2);

        glBindBuffer(GL_ARRAY_BUFFER, VBO2);

        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);



    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);

    glEnableVertexAttribArray(0);

 

Loop:




        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);



        lightingShader.Use();

        GLint objectColorLoc = glGetUniformLocation(lightingShader.Program, "objectColor");

        GLint lightColorLoc  = glGetUniformLocation(lightingShader.Program, "lightColor");

        GLint lightPosLoc    = glGetUniformLocation(lightingShader.Program, "lightPos");

        GLint viewPosLoc     = glGetUniformLocation(lightingShader.Program, "viewPos");

        glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);

        glUniform3f(lightColorLoc,  1.0f, 1.0f, 1.0f);

        glUniform3f(lightPosLoc,    lightPos.x, lightPos.y, lightPos.z);

        glUniform3f(viewPosLoc,     camera.Position.x, camera.Position.y, camera.Position.z);



  

        glm::mat4 view;

        view = camera.GetViewMatrix();

        glm::mat4 projection = glm::perspective(camera.Zoom, (GLfloat)WIDTH / (GLfloat)HEIGHT, 0.1f, 100.0f);



        GLint modelLoc = glGetUniformLocation(lightingShader.Program, "model");

        GLint viewLoc  = glGetUniformLocation(lightingShader.Program, "view");

        GLint projLoc  = glGetUniformLocation(lightingShader.Program, "projection");



        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));

        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));





       glBindVertexArray(containerVAO);

        glm::mat4 model;

        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));

        glDrawArrays(GL_TRIANGLES, 0, 36);











        lampShader.Use();



        modelLoc = glGetUniformLocation(lampShader.Program, "model");

        viewLoc  = glGetUniformLocation(lampShader.Program, "view");

        projLoc  = glGetUniformLocation(lampShader.Program, "projection");

        // Set matrices

        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));

        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

        model = glm::mat4();

        model = glm::translate(model, lightPos);

        model = glm::scale(model, glm::vec3(1.0f));

        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));



        glBindVertexArray(lampVAO);

        glDrawArrays(GL_TRIANGLES, 0, 36);



 
 

Vertex Shader (Regular Entity):


#version 330 core

layout (location = 0) in vec3 position;

layout (location = 1) in vec3 normal;



out vec3 Normal;

out vec3 FragPos;



uniform mat4 model;

uniform mat4 view;

uniform mat4 projection;



void main()

{

    gl_Position = projection * view *  model * vec4(position, 1.0f);

    FragPos = vec3(model * vec4(position, 1.0f));

    Normal = mat3(transpose(inverse(model))) * normal;  

}

Fragment Shader (Regular Entity)


#version 330 core

out vec4 color;



in vec3 FragPos;  

in vec3 Normal;  

 

uniform vec3 lightPos;

uniform vec3 viewPos;

uniform vec3 lightColor;

uniform vec3 objectColor;



void main()

{

    // Ambient

    float ambientStrength = 0.1f;

    vec3 ambient = ambientStrength * lightColor;

      

    // Diffuse

    vec3 norm = normalize(Normal);

    vec3 lightDir = normalize(lightPos - FragPos);

    float diff = max(dot(norm, lightDir), 0.0);

    vec3 diffuse = diff * lightColor;

    

    // Specular

    float specularStrength = 0.5f;

    vec3 viewDir = normalize(viewPos - FragPos);

    vec3 reflectDir = reflect(-lightDir, norm);  

    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);

    vec3 specular = specularStrength * spec * lightColor;  

        

    vec3 result = (ambient + diffuse + specular) * objectColor;

    color = vec4(result, 1.0f);

}

Vertex Shader (Lamp):




#version 330 core



layout (location = 0) in vec3 position;



uniform mat4 model;

uniform mat4 view;

uniform mat4 projection;



void main()

{

    gl_Position = projection * view * model * vec4(position, 1.0f);

}

Fragment Shader (Lamp):




#version 330 core



out vec4 color;



void main()

{

    color = vec4(1.0f); 

}

 

I used to think it was

Loop:


glbindVertexArray(lampVAO);        // <-- where glDrawArray would draw VAO that was last bound?
glDrawArray(GL_TRIANGLES, 0, 36);

But then I started tinkering with the above code and commented out both glbindVertexArray commands (lamp and regular entity) in loop and commented out the lamp vertex array during initialization in order to remove it from the scene.


 
 

    //   glBindVertexArray(containerVAO);



        glm::mat4 model;



        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));



       glDrawArrays(GL_TRIANGLES, 0, 36);









        lampShader.Use();







        modelLoc = glGetUniformLocation(lampShader.Program, "model");



        viewLoc  = glGetUniformLocation(lampShader.Program, "view");



        projLoc  = glGetUniformLocation(lampShader.Program, "projection");



        // Set matrices



        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));



        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));



        model = glm::mat4();



        model = glm::translate(model, lightPos);



        model = glm::scale(model, glm::vec3(1.0f));



        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));







     //   glBindVertexArray(lampVAO);



        glDrawArrays(GL_TRIANGLES, 0, 36);

However, both cubes still rendered.

In a nutshell, I'm trying to understand the OpenGL equivalent of the following SFML code


sf::RenderWindow Window();
sf::RectangleShape rect;
 
while(Window.isOpen()){
 
Window.draw(rect); // <-- SFML knows to draw the rect entity to the display due to this command.
}
 

Note: I understand OpenGL does not have


Window.draw(rect);

The above code has wrapped around the OpenGL commands. Looking for the GL equivalent of draw this entity.

That part of the source code is admittedly a bit hard to follow. I'm not sure if I'd like to design it that way, but alas.

RectangleShape is-a Shape, is-a Drawable. Window is-a RenderTarget, which has a member function draw. This one calls the draw member on the respective Drawable, which in turn calls back RenderTarget's draw function with m_vertices as parameter... which is *drum roll* a class intriguingly called VertexArray. Which, again, is-a Drawable that again calls back to the RenderTarget's draw function, this time with a different overload, passing a std::vector of vertices, a size, a primitive type, and texture state.

So.... long story short, the bottom line of this convoluted going up and down class hierarchies is simply calling glDrawArrays on the four corner points of the rectangle. The relevant code can be found in RenderTarget.cpp, lines 200 to 300.

Advertisement
OpenGL has a bunch of internal state. You configure the state then make draw calls. Both cubes use the same vertex information. In your setup code you have this.
glBindVertexArray(containerVAO);

    // Position attribute

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);

    glEnableVertexAttribArray(0);

    // Normal attribute

    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));

    glEnableVertexAttribArray(1);
It will configure OpenGL to use that vertex information. First you bind a vertex array, then with glVertrexAttribPointer you tell OpenGL how the positions and normals are packed into that array. Inside your loop, the glUniform... functions such as glUniform3f and glUniformMatrix4fv are used to configure the shader and in your specific case control the position and color of the geometry being rendered.
My current game project Platform RPG

So as long as the number of glDrawArray commands match the number of VAO, OpenGL will know internally which VAO you are referring to and draw to the display?

So I do not need to

glBindVertexArray
every frame?
It will use the last bound vertex buffer so you will probably want to be binding every frame, you need to bind whenever you want to change the source of data. If you will only ever be rendering cubes then you won't need to rebind, but you probably will want to be rebinding.
My current game project Platform RPG

So as long as the number of glDrawArray commands match the number of VAO, OpenGL will know internally which VAO you are referring to and draw to the display?

So I do not need to


glBindVertexArray
every frame?

When you draw you're essentially asking OpenGL to take the data in whatever vertex buffer(VBO/vertex data) is bound, and draw it. It draws using the state you have set up at the time you issue the draw command, a simple way to think of it is that it has a bunch of global variables with different information set about how to draw the thing.

A VAO is like a shortcut to set that state. You either have a VAO currently bound or you don't, when it's bound and you change state it "records" that state. So if you want to set a VAO up you'd create it, then bind it, then set your state, then unbind it(bind the 0 id VAO.) Then whenever you want to draw you just bind it again, and voila, all the settings are set.

However since the VAO "records" the state changes you make, that means you generally need to unbind it between draw calls, because if you don't then if some other code later on changes some state, it's going to change the settings on your VAO, so when you get back to using it, it won't have the same setup.

EDIT: I should probably clarify that in large scale you wouldn't actually want to bind and unbind a separate VAO for every single draw call. The point of using VAO's is so that you can switch between sets of similar drawing settings(shader inputs and such) easily and efficiently.

Link here explains the basic idea.

This topic is closed to new replies.

Advertisement