Learn OpenGL. Part 2.3. - Materials


A couple of days ago, I came across the first article in this series of lessons. Unfortunately, only the initial lessons have been translated, and all the most interesting (SSAO, PBR, shadows) are only ahead. This course was translated by three people (now four), and I hope that one of the readers will help with the translation of the remaining parts. Unfortunately, I am not a professional translator, so there may be errors of a different nature in the text. I would be glad if you report them. Enjoy reading!


In the real world, every object reacts differently to light. Iron objects usually sparkle stronger than, for example, a clay vase. A wooden container does not react to light in the same way as a steel one. Each object has a different reflectivity. Some objects reflect light without strong scattering, while others have a rather large reflection radius. If we want to simulate different types of objects in OpenGL, then we need to determine the material properties specific to each object.

In the previous lesson, we set the color of light to determine the appearance of an object, combined with a background and reflective component. Describing objects, we can set the color of the material for all three components of lighting: ambient ( ambient ), scattered ( the diffuse ) and specular ( specular ). After that, we will get detailed control over the resulting color of the object. Now add strength gloss ( shininess ) to our three colors and get all the properties of the material that we need:

#version 330 core
struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
uniform Material material;

In the fragment shader, we create a structure that stores the properties of the material of the object. Of course, we can store from as independent uniform variables , but this is less organized. First, we define the structure, and then simply declare a unifrom variable with the type of the structure we just created.

As you can see, we define a color vector for each lighting component by Phong . The vector ambientdetermines what color the object reflects under the background lighting. This is usually the color of the object itself. A vector diffusedetermines the color of an object under diffuse lighting. As well as background, it determines the desired color of the object. The vector specularsets the flare color on the object, and the variable sets the shininessradius of this flare.

Using these four components, we can simulate many real materials. The table on devernay.free.fr contains the properties of some materials that we can see in real life. The following image shows our cube, with different materials:

As you may have noticed, correctly selected properties of materials completely transform our cube. Of course, this is visible to the naked eye, but for greater realism, we still need a more interesting figure. In the next section of the tutorials, we will discuss loading 3d models of any complexity.

The selection of the right materials for the object is a complex art that requires a lot of experience and experimentation. Therefore, it is not uncommon for cases when the visual quality of an object is completely lost if they select the wrong material.

Let's try to implement a material system in shaders.

Material customization

We created the material structure in the shader fragment, so we need to take this into account in further lighting calculations. Since all material variables are stored in the structure, we can access them through a uniform variablematerial :

void main()
    // ambient
    vec3 ambient = lightColor * material.ambient;
    // diffuse 
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = lightColor * (diff * material.diffuse);
    // specular
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);  
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = lightColor * (spec * material.specular);  
    vec3 result = ambient + diffuse + specular;
    FragColor = vec4(result, 1.0);

As you may have noticed, now in the calculations of lighting we use the properties of the structure material, therefore, this time the resulting color depends on the material we determined. Each component of the material of the object is multiplied by its corresponding component of light.

We can customize the material of the object in the application by setting the value of the appropriate uniform variables . However, there is no special way in GLSL to assign values ​​to structures. A structure merely encapsulates uniform variables . Therefore, if we want to fill it, we must set the value of each uniform variable in the same way as we did before, but this time with the structure name prefix:

lightingShader.setVec3("material.ambient",  1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.diffuse",  1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
lightingShader.setFloat("material.shininess", 32.0f);

We set the background and scattered components to the color of the object itself, and set the highlight to moderately bright. We do not need him to stand out. Also, we left the gloss power equal to 32. Now we can easily influence the material of the object from the application.

Running the program will produce the following results:

It doesn't look very realistic so far, right?

Properties of light

Now our object is too bright. The reason for this lies in the fact that the background, diffused and glare colors are reflected with full force from any light source. However, light sources can also have different intensities. In the previous lesson, we solved this problem by replacing the background and scattered components with constant values. Now we need to do something similar, but now for each component of the light. If we imagine lightColorhow vec3(1.0), then the code will turn out like this:

vec3 ambient  = vec3(1.0) * material.ambient;
vec3 diffuse  = vec3(1.0) * (diff * material.diffuse);
vec3 specular = vec3(1.0) * (spec * material.specular); 

These values ​​( vec3(1.0)) can be changed individually for each light source, and this is exactly what we need. Now the color of the background component completely affects the color of the cube, but the background component should not affect the resulting color so much, so make it a little smaller:

vec3 ambient = vec3(0.1) * material.ambient;  

We can influence the diffuse and flare component of light in the same way. This is closely related to what we did in the previous lesson. Now we need to create something similar to the structure of the material, but now for light:

struct Light {
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
uniform Light light;  

A light source has different intensities for background , diffuse, and flare light. Usually the background light intensity is pretty low, we don’t want it to dominate too much? The color of the diffuse component is the color of the light source itself; often it is bright white. The mirror component is usually left equal vec3(1.0). Note that we also added a light position vector to the structure.

Now update the lighting calculations in the fragment shader:

vec3 ambient  = light.ambient * material.ambient;
vec3 diffuse  = light.diffuse * (diff * material.diffuse);
vec3 specular = light.specular * (spec * material.specular);  

We also need to set the light intensity in the application itself:

lightingShader.setVec3("light.ambient",  0.2f, 0.2f, 0.2f);
lightingShader.setVec3("light.diffuse",  0.5f, 0.5f, 0.5f); // darken the light a bit to fit the scene
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f); 

Now we have modeled the effect of light on the material of the object and got a result similar to the result of the previous lesson. However, now we have full control over the lighting and material of the object:

Теперь изменять внешний вид объекта относительно несложно.

Различные цвета света

До сих пор цвет нашего источника света варьировался от белого до черного, по этому мы не могли изменить цвет самого объекта (только его интенсивность). Однако, с этого момента у нас есть простой доступ к свойствам света, по этому мы можем изменять его с течением времени, чтобы получать такие прикольные результаты:

Как вы могли заметить, изменение света влияет на то, какие цвета объект может отразить (наверное, вы это помните из урока, посвященного цветам), а это, в свою очередь, существенно влияет на результат.

Мы с легкостью можем время от времени варьировать свет, изменяя фоновый и диффузный цвета через sin и glfwGetTime:

glm::vec3 lightColor;
lightColor.x = sin(glfwGetTime() * 2.0f);
lightColor.y = sin(glfwGetTime() * 0.7f);
lightColor.z = sin(glfwGetTime() * 1.3f);
glm::vec3 diffuseColor = lightColor   * glm::vec3(0.5f); // decrease the influence
glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // low influence
lightingShader.setVec3("light.ambient", ambientColor);
lightingShader.setVec3("light.diffuse", diffuseColor);

Try and experiment with lighting and materials and you will see how they affect the result. You can find the source code of the application here .


  • Can you model some real objects by endowing them with the appropriate materials that we saw at the beginning of this lesson? Please note that the tabular values ​​of the background light do not coincide with the diffuse ones - they do not take into account the light intensity. To get the correct results, you need to set the intensity of each light component equal vec3(1.0). Solution for a blue-green plastic container.

Also popular now: