Translation of the next lesson from learnopengl.com. I recently discovered Russian OpenGL tutorials from the OGLDev website , but some of them require version 4 of opengl, and the code samples are too dependent on previous lessons and object-oriented. Therefore, to the attention of all interested beginners with old iron with an old iron, I propose a short article on color with which the second part of the training course from Joey de Vries begins:
In the previous lessons there were brief references to how to work with color in OpenGL, but so far we have only touched the surface of this question. Now we will discuss in detail what color is, and begin building the stage, which we will use in the next lessons on lighting.
In the real world, each object has its own color, which can take on virtually any value. In the digital world, we need to display the (infinite) colors of reality through (limited range) 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. Numerically, colors represent a combination of three components: red, green, and blue, usually 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 the 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 colors of the light reflected by them; those. we perceive colors that remain unabsorbed (discarded) by an 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 it in the picture). Thus, if we shine a white light on a blue toy, it will absorb all the components of white light in color except blue. Since the toy does not absorb the blue color, it will be reflected, and this reflected light, acting on our organs of vision, gives the impression that the toy is colored blue. The following figure illustrates this phenomenon by the example of a coral-colored object reflecting the original colors with different intensities:
You can see that white sunlight is actually a collection of all visible colors, and the object absorbs the largest part 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 specify its color. In the previous paragraph, we talked about white sunlight, so let's set our color to our light source too. If we then 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 us return to our toy (this time of coral color) and see how to calculate its color, perceived by an observer, by graphic means. To get the color we need, we produce 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 an object by a vector, characterizing the magnitude of the reflection of the color components coming from the light source. What would happen if we used green light to illuminate?
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. One half of the total amount of green light toy absorbs, and the second reflects. Therefore, the color of the toy that we will see will be dark green. Thus, if we use a green light source, only the green components will be reflected and perceived; no red and blue hues will be visible. As a result, the object of coral color suddenly becomes dark greenish. Let's do another experiment with a dark olive green:
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 obtaining a strange color 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 down to creating a scene in which we can practice.
In the following lessons, working in depth with colors, we will create interesting visual effects that simulate lighting in the real world. From now on, to simulate the lighting, we will use at least one light source, and we will depict it as a visible object of the scene.
The first thing we need is an illuminated object, in the capacity of which we will take a cube already familiar to us from previous lessons. We will also need some bright object to show where the light source is in the 3D scene. For simplicity, we will also present the light source as a cube (after all, we already have the coordinates of the vertices , right?).
Since the creation of a VBO (vertex buffer object), the installation of pointers of vertex attributes and the execution of all other tricky operations for you now should not be difficult, we will not stop there. If these topics still cause you difficulties, then before proceeding, I recommend that you familiarize yourself with previous lessons and, if possible, perform the suggested exercises.
So let's start with a vertex shader to draw a container. The coordinates of the container's vertices remain old (although this time we will not need the texture coordinates), so there will be nothing new in the code. We use the “trimmed” version of the vertex shader from the latest 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 according to the new vertex shader (however, if you want, you can leave the texture coordinates buffer and the active pointer to these attributes in the VAO; we just won't use them yet, but and to start everything “from scratch” is also not such a bad idea).
Since we are going to add a cube lamp to the scene, we need to create a new VAO for it. We could portray the lamp with the same VAO, transforming the model matrix, but in future lessons we will quite often change the vertex and attribute data of the container object and at the same time do not want the changes to affect the lamp object (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 lamp-cube, 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); }
Fragment shader through uniform variables receives two parameters: the color of the object and the color of the light source. As we discussed at the beginning of this lesson, to obtain a perceived (i.e. reflected by the object) color, we multiply the vector of the light source by the vector of the color of the object. Again, the shader source should not raise questions.
Let's give the object the 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 be noted 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 following lessons unnecessary inconveniences will appear if the calculation of illumination will influence the color of the lamp, so we prefer to separate the color of the lamp from everything else. Let us make the lamp a constant bright color that is 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 to draw the lamp, and thus 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 to the vertex shader of the lamp. A fragmentary shader will provide the lamp brightness by specifying 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 apply lamp shaders. In the other lessons, we will gradually improve the lighting shaders, and eventually we will achieve more realistic results.
The main purpose of the cube of the lamp 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 draw a cube at the location of the light source. The lamp is drawn using the lamp shader, and this ensures that the cube of the lamp will always remain white, regardless of the scene lighting conditions.
So, let's declare the 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 too much in the scene:
model = glm::mat4(); model = glm::translate(model, lightPos); model = glm::scale(model, glm::vec3(0.2f));
The resulting lamp drawing code should look something like this:
lampShader.Use(); // uniform- , ... // glBindVertexArray(lightVAO); glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0);
Inserting all the above code snippets into the appropriate places will give us a properly configured OpenGL scene for further experiments with lighting. With a successful compilation, everything should look like this:
Yes, there is not much to look at so far, but I promise that in the next lessons this scene will become much more attractive.
If it is difficult for you to understand how all the code fragments are combined with each other and combined into a ready-made program, then carefully study the source code and carefully follow the entire sequence of actions performed in it.
Now that we have enough knowledge about 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 will begin.
Source: https://habr.com/ru/post/329592/
All Articles