Support me!

003.) Adding Colors

Hi there! This is the third tutorial in OpenGL4 series and this one will be rather short. Definitely shorter than the first two . In this one, we will breathe life to our scene by adding nice interpolated colors. So let's get going!

To add colors to our scene, that look nice and are interpolated along whole polygon, we first have to define them for each vertex somehow. So if you remember explanation about vertex attributes in last tutorial, vertex color is just another vertex attribute, just like position! In this case, we will create another VBO, which will hold the color data . It is also possible to just have one VBO, containing both positions and colors, but I found this easier to read. You can try that as homework though.

Let's have a look at initializeScene() function:

void OpenGLWindow::initializeScene()

{

// ... loading shaders here

glGenVertexArrays(1, &mainVAO); // Creates one Vertex Array Object

glBindVertexArray(mainVAO);

glm::vec3 vTriangle[] = { glm::vec3(-0.4f, 0.1f, 0.0f), glm::vec3(0.4f, 0.1f, 0.0f), glm::vec3(0.0f, 0.7f, 0.0f) };

glm::vec3 vTriangleColors[] = { glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f),glm::vec3(0.0f, 0.0f, 1.0f) };

glm::vec3 vQuad[] = { glm::vec3(-0.2f, -0.1f, 0.0f), glm::vec3(-0.2f, -0.6f, 0.0f), glm::vec3(0.2f, -0.1f, 0.0f), glm::vec3(0.2f, -0.6f, 0.0f) };

glm::vec3 vQuadColors[] = { glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(1.0f, 0.5f, 0.0f) };

shapesVBO.createVBO();

shapesVBO.bindVBO();

shapesVBO.addData(vTriangle, sizeof(vTriangle));

shapesVBO.addData(vQuad, sizeof(vQuad));

shapesVBO.uploadDataToGPU(GL_STATIC_DRAW);

glEnableVertexAttribArray(0);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), 0);

colorsVBO.createVBO();

colorsVBO.bindVBO();

colorsVBO.addData(vTriangleColors, sizeof(glm::vec3) * 3);

colorsVBO.addData(vQuadColors, sizeof(glm::vec3) * 4);

colorsVBO.uploadDataToGPU(GL_STATIC_DRAW);

glEnableVertexAttribArray(1);

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), 0);

}

{

// ... loading shaders here

glGenVertexArrays(1, &mainVAO); // Creates one Vertex Array Object

glBindVertexArray(mainVAO);

glm::vec3 vTriangle[] = { glm::vec3(-0.4f, 0.1f, 0.0f), glm::vec3(0.4f, 0.1f, 0.0f), glm::vec3(0.0f, 0.7f, 0.0f) };

glm::vec3 vTriangleColors[] = { glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f),glm::vec3(0.0f, 0.0f, 1.0f) };

glm::vec3 vQuad[] = { glm::vec3(-0.2f, -0.1f, 0.0f), glm::vec3(-0.2f, -0.6f, 0.0f), glm::vec3(0.2f, -0.1f, 0.0f), glm::vec3(0.2f, -0.6f, 0.0f) };

glm::vec3 vQuadColors[] = { glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f), glm::vec3(1.0f, 0.5f, 0.0f) };

shapesVBO.createVBO();

shapesVBO.bindVBO();

shapesVBO.addData(vTriangle, sizeof(vTriangle));

shapesVBO.addData(vQuad, sizeof(vQuad));

shapesVBO.uploadDataToGPU(GL_STATIC_DRAW);

glEnableVertexAttribArray(0);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), 0);

colorsVBO.createVBO();

colorsVBO.bindVBO();

colorsVBO.addData(vTriangleColors, sizeof(glm::vec3) * 3);

colorsVBO.addData(vQuadColors, sizeof(glm::vec3) * 4);

colorsVBO.uploadDataToGPU(GL_STATIC_DRAW);

glEnableVertexAttribArray(1);

glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), 0);

}

What can we see here? We can see, that I have defined two arrays for each polygon - triangle vertices and triangle colors, then quad vertices and quad colors (**DISCLAIMER:** I know it ain't very systematic approach to just ad-hoc define the geometry, but in tutorials later it will get better, trust me . This is a very simple example still, so that's why). After that, we simply create two VBOs - one holds vertex positions, one holds vertex colors. The one essential difference is, that positions is attribute with index 0 (glEnableVertexAttribArray(0)) and colors is attribute with index 1 glEnableVertexAttribArray(1). And both attributes are nicely packed together using mainVAO vertex array object .

You won't believe it, but we're halfway there! Now, the second important part is not here, but in the shaders instead. So let's investigate shaders now.

#version 440 core

layout(location = 0) in vec3 vertexPosition;

layout(location = 1) in vec3 vertexColor;

smooth out vec3 ioVertexColor;

void main()

{

gl_Position = vec4(vertexPosition, 1.0);

ioVertexColor = vertexColor;

}

layout(location = 0) in vec3 vertexPosition;

layout(location = 1) in vec3 vertexColor;

smooth out vec3 ioVertexColor;

void main()

{

gl_Position = vec4(vertexPosition, 1.0);

ioVertexColor = vertexColor;

}

Important thing to notice here is, that now we have two layout(location) lines - one for vertex position, and one for vertex color. The most important line of this tutorial follows - smooth out vec3 ioVertexColor. What does this line mean? First of all, we are naming this variable ioVertexColor - IO abbreviation means in most programming languages / environments input and output. And that's exactly the same case here! In vertex shader, we will **OUTPUT** this variable further (that is why it is defined with keyword out too!) and in the fragment shader later, we will receive it, so there it will be **INPUT** variable! The keyword smooth plays a very important role here - it tells OpenGL, that we actually want our values to be interpolated among fragments. So that red color in one vertex goes to greenish color in second vertex and closer we get to the third vertex, the more blueish it gets . In the main method, don't forget to assign our input color to the smoothed variable ioVertexColor!

#version 440 core

layout(location = 0) out vec4 outputColor;

smooth in vec3 ioVertexColor;

void main()

{

outputColor = vec4(ioVertexColor, 1.0);

}

layout(location = 0) out vec4 outputColor;

smooth in vec3 ioVertexColor;

void main()

{

outputColor = vec4(ioVertexColor, 1.0);

}

As before, we have one output from fragment shader - layout(location = 0) out vec4 outputColor, or the color, that will actually be seen on the screen. But the next line is the most important - smooth in vec3 ioVertexColor. It's the same variable, that we have output in the vertex shader! The only difference here is, that in vertex shader, it was bound with keyword out (because we have output there), and here, in the fragment shader, it is bound with keyword in (because we receive / read it). But what is really cool is, that this variable has already interpolated color! Then, in the main method, we simply assign this interpolated color to the outputColor and that's it, nothing else ! Important thing to mention is, that the name of output variable in one stage (vertex shader) should be same as name of input variable in another stage (framgent shader). That is why I chose that name ioVertexColor, because it should be clear from it, that we have multi-stage variable there.

And that is really it! I don't know what else could I mention here. The resulting render looks really nice :

This tutorial has been really more of a chill-torial, I guess reading through this was not exhausting at all . Next time, we are finally getting into business by Entering Third Dimension! So go there right now!