learnopengl. Lesson 2.1 - Colors

  • Tutorial

Translation of another lesson from learnopengl.com. I recently discovered OpenGL Tutorials in Russian from the OGLDev website , but some of them require version 4 of opengl, and the code examples are too dependent on previous lessons and are object-oriented. Therefore, to the attention of all beginners with an old iron who are interested in opengl, I offer a short article on color, with which the second part of the training course from Joey de Vries begins:

Colors


In previous tutorials, there were brief references to how to work with color in OpenGL, but so far we have only touched on the very surface of this issue. Now we will discuss in detail what color is like and start building the scene, which we will use in the next lessons on lighting.



In the real world, each object has its own color, which can take on almost any value. In the digital world, we need to display the (infinite) colors of reality by means of a (limited range) of digital values, so not all existing colors can be reproduced in digital form. However, we can display so many colors that you probably won't notice the difference anyway. Digitally, colors represent a combination of three components: red, green, and blue, commonly abbreviated as RGB (Red Green Blue). Using various combinations of just these three values, we can convey almost any existing color. For example, to get a coral color, we define a color vector as follows:


glm::vec3 coral(1.0f, 0.5f, 0.31f);

The colors we see in real life are not the colors of the objects themselves, but the color of the light reflected by them; those. we perceive colors that remain not absorbed (discarded) by the object. For example, the light of the sun is perceived as white light, which consists of all the colors of the spectrum (you can see this in the picture). Thus, if we shine white light on a blue toy, then it will absorb all the colors that make up the white light, except for blue. Since the toy does not absorb the blue color, it will be reflected, and this reflected light, acting on our organs of vision, creates the impression that the toy is painted in blue. The following figure illustrates this phenomenon with an example of an object of coral color, reflecting the original colors with different intensities:


Reflection and Absorption of Colors

You can see that white sunlight is actually a combination of all visible colors, and the object absorbs most of this range. It reflects only those colors, the combination of which is perceived by us as the color of the object (in this case, coral color).


These rules of light reflection are directly applied in computer graphics. When we create a light source in OpenGL, we indicate its color. In the previous paragraph we talked about white sunlight, so let's set our light source to white too. If then we multiply the color of the light source by the color of the object, then the resulting value will be the reflected color of the object (and, therefore, the color in which we perceive the object). Let's go back to our toy (this time coral color) and see how to graphically calculate its color, perceived by the observer. To get the color we need, we will perform componentwise multiplication of two color vectors:


glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);

The toy absorbs most of the white light, and the remaining amount of red, green and blue radiation, it reflects the color of its surface. This is a demonstration of how colors behave in the real world. Thus, we can set the color of the object with a vector characterizing the reflection values ​​of the color components coming from the light source. And what would happen if we used green light for lighting?


glm::vec3 lightColor(0.0f, 1.0f, 0.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.0f, 0.5f, 0.0f);

As we see, there is no red and blue component in the light source, so the toy will not absorb and / or reflect them. The toy absorbs one half of the total amount of green light, and reflects the second. Therefore, the color of the toy that we will see will be dark green. Thus, if we use a green light source, then only the green components will be reflected and perceived; no red or blue shades will be visible. As a result, the coral-colored object suddenly turns dark green. Let's do another experiment with a dark olive green light:


glm::vec3 lightColor(0.33f, 0.42f, 0.18f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.33f, 0.21f, 0.06f);

Here is an example of getting a strange coloring of an object due to the use of multi-colored lighting. It turns out to be an original colorist is not at all difficult.


But stop talking about flowers, let's get started on creating a scene in which we can practice.


Lighted stage


In the following lessons, working in depth with colors, we will create interesting visual effects that mimic lighting in the real world. From now on, to simulate lighting, we will use at least one light source, and we will portray it as a visible object in the scene.


The first thing we need is an illuminated object, for which we will take a cube already familiar to us from previous lessons. We will also need some bright object in order to show where the light source is in the 3D scene. For simplicity, we also present the light source as a cube (because we already have the coordinates of the vertices , is not it?).


Since creating a VBO (vertex buffer object), setting vertex attribute pointers and performing all other tricky operations should not be difficult for you now, we will not stop there. If these topics still cause difficulties for you, then before continuing, I recommend that you familiarize yourself with previous lessons and, if possible, complete the proposed exercises.


So, let's start with the vertex shader to draw the container. The coordinates of the container vertices remain old (although this time we do not need texture coordinates), so there will be nothing new in the code. We use the "stripped down" version of the vertex shader from the last lessons:


#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);
}

Make sure that you update the vertex data and attribute pointers in accordance with the new vertex shader (however, if you want, you can leave a buffer with texture coordinates and an active pointer to these attributes in VAO; we just won’t use them yet, but and starting from scratch is also not such a bad idea).


Since we are going to add a cube lamp to the scene, we will need to create a new VAO for it. We could depict the lamp with the same VAO, transforming the model matrix, but in future lessons we will quite often change the data of the vertices and attributes of the container object and at the same time do not want the changes to affect the lamp object (in it we are only interested in the coordinates of the lamp vertices ), so let's create another VAO:


GLuint lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
// Так как VBO объекта-контейнера уже содержит все необходимые данные, то нам нужно только связать с ним новый VAO 
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// Настраиваем атрибуты (нашей лампе понадобятся только координаты вершин)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);

This code is relatively simple. Now that we have created both the container and the cube lamp, one more thing remains to be done - the fragment shader:


#version 330 core
out vec4 color;
uniform vec3 objectColor;
uniform vec3 lightColor;
void main()
{
    color = vec4(lightColor * objectColor, 1.0f);
}

The fragment shader receives two parameters through uniform variables: the color of the object and the color of the light source. As we discussed at the beginning of this lesson, to obtain the perceived (i.e., reflected by the object) color, we multiply the vector of the light source by the color vector of the object. And again, the shader source should not raise questions.

Let's give the object a coral color from the previous section:


// Не забудьте перед установкой uniform-переменных для соответствующей шейдерной программы вызвать glUseProgram 
GLint objectColorLoc = glGetUniformLocation(lightingShader.Program, "objectColor");
GLint lightColorLoc  = glGetUniformLocation(lightingShader.Program, "lightColor");
glUniform3f(objectColorLoc, 1.0f, 0.5f, 0.31f);
glUniform3f(lightColorLoc,  1.0f, 1.0f, 1.0f); // зададим цвет источника света (белый)

It remains to note that if we start editing the vertex and fragment shaders, the cube of the lamp will also change, and this is not at all what we want. In the next lesson, there will be unnecessary inconvenience if the calculation of the illumination will affect the color of the lamp, so we prefer to separate the color of the lamp from everything else. Let's make the lamp have a constant bright color, not affected by other color changes (from this the cube of the lamp will look as if it really is a source of light).


To achieve this, we will create a second set of shaders, which we will use only for drawing the lamp, and thereby protect ourselves from any changes in the shaders that calculate the lighting of the scene. The vertex shader will remain unchanged, so you can simply copy its source code into the vertex shader of the lamp. And the fragment shader will provide the brightness of the lamp by setting the constant white color of the fragments:


#version 330 core
out vec4 color;
void main()
{
    color = vec4(1.0f); // Устанавливает все 4 компоненты вектора равными 1.0f
}

When we draw our cube container (or maybe a lot of containers), we will use the lighting shader created in this tutorial, and when we want to draw a lamp, we will use the lamp shaders. In other lessons, we will focus on the gradual improvement of lighting shaders, and eventually achieve more realistic results.


The main purpose of the lamp cube is to show the location of the light source. We set the position of the light source somewhere in the scene, but this is only the coordinate of a point that has no visual representation. To really show the lamp, we will draw a cube at the location of the light source. The lamp is drawn using the lamp shader, and this ensures that the lamp cube always remains white, regardless of the lighting conditions of the scene.


So, let's declare a global variable vec3 , which will represent the position of the light source in the coordinates of world space:


glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

Now, before drawing the lamp, move it to the position of the light source and slightly reduce the size of the cube to make sure that the lamp does not dominate the scene too much:


model = glm::mat4();
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f));

Получившийся в результате код рисования лампы должен выглядеть примерно так:


lampShader.Use();
// Установка uniform-переменных матриц модели, вида и проекции
...
// Рисование куба лампы
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);			
glBindVertexArray(0);

Вставка всех приведенных выше фрагментов кода в соответствующие места, даст нам правильно настроенную OpenGL-сцену для дальнейших экспериментов с освещением. При успешной компиляции все должно выглядеть следующим образом:


Preliminary scene

Да, смотреть пока особо не на что, но обещаю, что в следующих уроках эта сцена станет гораздо более привлекательной.


Если вам сложно разобраться с тем, как все фрагменты кода сочетаются друг с другом и объединяются в готовую программу, тогда тщательно изучите исходный код и внимательно проследите всю последовательность совершаемых в нем действий.


Now that we have enough knowledge of color and there is a basic scene for a more intimate acquaintance with the light, we can move on to the next lesson, in which real magic begins.


Also popular now: