LearnOpenGL

Translation in progress of learnopengl.com.
git clone https://git.mtkn.jp/LearnOpenGL
Log | Files | Refs

Lighting-maps.html (14282B)


      1     <h1 id="content-title">Lighting maps</h1>
      2 <h1 id="content-url" style='display:none;'>Lighting/Lighting-maps</h1>
      3 <p>
      4   In the <a href="https://learnopengl.com/Lighting/Materials" target="_blank">previous</a> chapter we discussed the possibility of each object having a unique material of its own that reacts differently to light. This is great for giving each object a unique look in comparison to other objects, but still doesn't offer much flexibility on the visual output of an object. 
      5 </p>
      6 
      7 <p>
      8   In the previous chapter we defined a material for an entire object as a whole. Objects in the real world however usually do not consist of a single material, but of several materials. Think of a car: its exterior consists of a shiny fabric, it has windows that partly reflect the surrounding  environment, its tires are all but shiny so they don't have specular highlights and it has rims that are super shiny (if you actually washed your car alright). The car also has diffuse and ambient colors that are not the same for the entire object; a car displays many different ambient/diffuse colors. All by all, such an object has different material properties for each of its different parts.
      9 </p>
     10 
     11 <p>
     12   So the material system in the previous chapter isn't sufficient for all but the simplest models so we need to extend the system by introducing <em>diffuse</em> and <em>specular</em> maps. These allow us to influence the diffuse (and indirectly the ambient component since they should be the same anyways) and the specular component of an object with much more precision.
     13 </p>
     14 
     15 <h1>Diffuse maps</h1>
     16 <p>
     17   What we want is some way to set the diffuse colors of an object for each individual fragment. Some sort of system where we can retrieve a color value based on the fragment's position on the object?
     18 </p>
     19 
     20 <p>
     21   This should probably all sound familiar and we've been using such a system for a while now. This sounds just like <em>textures</em> we've extensively discussed in one of the <a href="https://learnopengl.com/Getting-started/Textures" target="_blank">earlier</a> chapters and it basically is just that: a texture. We're just using a different name for the same underlying principle: using an image wrapped around an object that we can index for unique color values per fragment. In lit scenes this is usually called a <def>diffuse map</def> (this is generally how 3D artists call them before PBR) since a texture image represents all of the object's diffuse colors.
     22 </p>
     23 
     24 <p>
     25   To demonstrate diffuse maps we're going to use the <a href="/img/textures/container2.png" target="_blank">following image</a> of a wooden container with a steel border:
     26 </p>
     27 
     28 <img src="/img/textures/container2.png" style="width:300px; height:300px"/>
     29 
     30 <p>
     31   Using a diffuse map in shaders is exactly like we showed in the texture chapter. This time however we store the texture as a <code>sampler2D</code> inside the <fun>Material</fun> struct. We replace the earlier defined <code>vec3</code> diffuse color vector with the diffuse map. 
     32 </p>
     33 
     34 <warning>Keep in mind that <code>sampler2D</code> is a so called <def>opaque type</def> which means we can't instantiate these types, but only define them as uniforms. If the struct would be instantiated other than as a uniform (like a function parameter) GLSL could throw strange errors; the same thus applies to any struct holding such opaque types.
     35 </warning>
     36 
     37 <p>
     38   We also remove the ambient material color vector since the ambient color is equal to the diffuse color anyways now that we control ambient with the light. So there's no need to store it separately:
     39 </p>
     40 
     41 <pre><code>
     42 struct Material {
     43     sampler2D diffuse;
     44     vec3      specular;
     45     float     shininess;
     46 }; 
     47 ...
     48 in vec2 TexCoords;
     49 </code></pre>
     50 
     51 <note>
     52   If you're a bit stubborn and still want to set the ambient colors to a different value (other than the diffuse value) you can keep the ambient <code>vec3</code>, but then the ambient colors would still remain the same for the entire object. To get different ambient values for each fragment you'd have to use another texture for ambient values alone.
     53 </note>
     54 
     55 <p>
     56   Note that we are going to need texture coordinates again in the fragment shader, so we declared an extra input variable. Then we simply sample from the texture to retrieve the fragment's diffuse color value:
     57 </p>
     58 
     59 <pre><code>
     60 vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));  
     61 </code></pre>
     62 
     63 <p>
     64   Also, don't forget to set the ambient material's color equal to the diffuse material's color as well:
     65 </p>
     66 
     67 <pre><code>
     68 vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
     69 </code></pre>
     70 
     71 <p>
     72   And that's all it takes to use a diffuse map. As you can see it is nothing new, but it does provide a dramatic increase in visual quality. To get it working we do need to update the vertex data with texture coordinates, transfer them as vertex attributes to the fragment shader, load the texture, and bind the texture to the appropriate texture unit. 
     73 </p>
     74 
     75 <p>
     76   The updated vertex data can be found <a href="/code_viewer.php?code=lighting/vertex_data_textures" target="_blank">here</a>. The vertex data now includes vertex positions, normal vectors, and texture coordinates for each of the cube's vertices. Let's update the vertex shader to accept texture coordinates as a vertex attribute and forward them to the fragment shader:
     77 </p>
     78 
     79 <pre><code>
     80 #version 330 core
     81 layout (location = 0) in vec3 aPos;
     82 layout (location = 1) in vec3 aNormal;
     83 layout (location = 2) in vec2 aTexCoords;
     84 ...
     85 out vec2 TexCoords;
     86 
     87 void main()
     88 {
     89     ...
     90     TexCoords = aTexCoords;
     91 }  
     92 </code></pre>
     93 
     94 <p>
     95   Be sure to update the vertex attribute pointers of both VAOs to match the new vertex data and load the container image as a texture. Before rendering the cube we want to assign the right texture unit to the <var>material.diffuse</var> uniform sampler and bind the container texture to this texture unit:
     96 </p>
     97 
     98 <pre><code>
     99 lightingShader.setInt("material.diffuse", 0);
    100 ...
    101 <function id='49'>glActiveTexture</function>(GL_TEXTURE0);
    102 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, diffuseMap);
    103 </code></pre>
    104 
    105 <p>
    106   Now using a diffuse map we get an enormous boost in detail again and this time the container really starts to shine (quite literally). Your container now probably looks something like this:
    107 </p>
    108 
    109 <img src="/img/lighting/materials_diffuse_map.png" class="clean"/>
    110 
    111 <p>
    112   You can find the full source code of the application <a href="/code_viewer_gh.php?code=src/2.lighting/4.1.lighting_maps_diffuse_map/lighting_maps_diffuse.cpp" target="_blank">here</a>. 
    113 </p>
    114 
    115 <h1>Specular maps</h1>
    116 <p>
    117   You probably noticed that the specular highlight looks a bit odd since the object is a container that mostly consists of wood and wood doesn't have specular highlights like that. We can fix this by setting the specular material of the object to <code>vec3(0.0)</code> but that would mean that the steel borders of the container would stop showing specular highlights as well and steel <strong>should</strong> show specular highlights. We would like to control what parts of the object should show a specular highlight with varying intensity. This is a problem that sounds familiar. Coincidence? I think not.
    118 </p>
    119 
    120 <p>
    121   We can also use a texture map just for specular highlights. This means we need to generate a black and white (or colors if you feel like it) texture that defines the specular intensities of each part of the object. An example of a <a href="/img/textures/container2_specular.png" target="_blank">specular map</a> is the following image:
    122 </p>
    123 
    124 <img src="/img/textures/container2_specular.png" style="width:300px; height:300px"/>
    125 
    126 <p>
    127   The intensity of the specular highlight comes from the brightness of each pixel in the image. Each pixel of the specular map can be displayed as a color vector where black represents the color vector <code>vec3(0.0)</code> and gray the color vector <code>vec3(0.5)</code> for example. In the fragment shader we then sample the corresponding color value and multiply this value with the light's specular intensity. The more 'white' a pixel is, the higher the result of the multiplication and thus the brighter the specular component of an object becomes.
    128 </p>
    129 
    130 <p>
    131   Because the container mostly consists of wood, and wood as a material should have no specular highlights, the entire wooden section of the diffuse texture was converted to black: black sections do not have any specular highlight. The steel border of the container has varying specular intensities with the steel itself being relatively susceptible to specular highlights while the cracks are not.
    132 </p>
    133   
    134 <note>
    135   Technically wood also has specular highlights although with a much lower shininess value (more light scattering) and less impact, but for learning purposes we can just pretend wood doesn't have any reaction to specular light.
    136 </note>
    137 
    138 <p>
    139 Using tools like <em>Photoshop</em> or <em>Gimp</em> it is relatively easy to transform a diffuse texture to a specular image like this by cutting out some parts, transforming it to black and white and increasing the brightness/contrast.
    140 </p>
    141 
    142 <h2>Sampling specular maps</h2>
    143 <p>
    144   A specular map is just like any other texture so the code is similar to the diffuse map code. Make sure to properly load the image and generate a texture object. Since we're using another texture sampler in the same fragment shader we have to use a different texture unit (see <a href="https://learnopengl.com/Getting-started/Textures" target="_blank">Textures</a>) for the specular map so let's bind it to the appropriate texture unit before rendering:
    145 </p>
    146 
    147 <pre><code>
    148 lightingShader.setInt("material.specular", 1);
    149 ...
    150 <function id='49'>glActiveTexture</function>(GL_TEXTURE1);
    151 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, specularMap);  
    152 </code></pre>
    153 
    154 <p>
    155   Then update the material properties of the fragment shader to accept a <code>sampler2D</code> as its specular component instead of a <code>vec3</code>:
    156 </p>
    157 
    158 <pre><code>
    159 struct Material {
    160     sampler2D diffuse;
    161     sampler2D specular;
    162     float     shininess;
    163 };  
    164 </code></pre>
    165   
    166 <p>
    167    And lastly we want to sample the specular map to retrieve the fragment's corresponding specular intensity: 
    168 </p>
    169   
    170 <pre><code>
    171 vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));
    172 vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));  
    173 vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
    174 FragColor = vec4(ambient + diffuse + specular, 1.0);   
    175 </code></pre>
    176   
    177 <p>
    178   By using a specular map we can specify with enormous detail what parts of an object have <em>shiny</em> properties and we can even control the corresponding intensity. Specular maps give us an added layer of control over lighting on top of the diffuse map. 
    179 </p>
    180 
    181 <note>
    182   If you don't want to be too mainstream you could also use actual colors in the specular map to not only set the specular intensity of each fragment, but also the color of the specular highlight. Realistically however, the color of the specular highlight is mostly determined by the light source itself so it wouldn't generate realistic visuals (that's why the images are usually black and white: we only care about the intensity).
    183 </note>
    184 
    185 <p>
    186   If you would now run the application you can clearly see that the container's material now closely resembles that of an actual wooden container with steel frames:
    187 </p>
    188 
    189 <img src="/img/lighting/materials_specular_map.png" class="clean"/>
    190 
    191 <p>
    192   You can find the full source code of the application <a href="/code_viewer_gh.php?code=src/2.lighting/4.2.lighting_maps_specular_map/lighting_maps_specular.cpp" target="_blank">here</a>.
    193 </p>
    194 
    195 <p>
    196    Using diffuse and specular maps we can really add an enormous amount of detail into relatively simple objects. We can even add more detail into the objects using other texture maps like <def>normal/bump maps</def> and/or <def>reflection maps</def>, but that is something we'll reserve  for later chapters. Show your container to all your friends and family and be content with the fact that our container can one day become even prettier than it already is! 
    197 </p>
    198   
    199 <h2>Exercises</h2>
    200 <p>
    201   <ul>
    202     <li>Fool around with the light source's ambient, diffuse and specular vectors and see how they affect the visual output of the container.</li>
    203     <li>Try inverting the color values of the specular map in the fragment shader so that the wood shows specular highlights and the steel borders do not (note that due to the cracks in the steel border the borders still show some specular highlight, although with less intensity): <a href="/code_viewer_gh.php?code=src/2.lighting/4.3.lighting_maps_exercise2/lighting_maps_exercise2.cpp" target="_blank">solution</a>.</li>
    204     <li>Try creating a specular map from the diffuse texture that uses actual colors instead of black and white and see that the result doesn't look too realistic. You can use this <a href="/img/lighting/lighting_maps_specular_color.png" target="_blank">colored specular map</a> if you can't generate one yourself: <a href="/img/lighting/lighting_maps_exercise3.png" target="_blank">result</a>.</li>
    205     <li>
    206       Also add something they call an <def>emission map</def> which is a texture that stores emission values per fragment. Emission values are colors an object may <em>emit</em> as if it contains a light source itself; this way an object can glow regardless of the light conditions. Emission maps are often what you see when objects in a game glow (like <a href="/img/lighting/lighting_maps_eyes_robot.jpg" target="_blank">eyes of a robot</a>, or <a href="/img/lighting/lighting_maps_strips_container.png" target="_blank">light strips on a container</a>). Add the <a href="/img/textures/matrix.jpg" target="_blank">following</a> texture (by creativesam) as an emission map onto the container as if the letters emit light: <a href="/code_viewer_gh.php?code=src/2.lighting/4.4.lighting_maps_exercise4/lighting_maps_exercise4.cpp" target="_blank">solution</a>; <a href="/img/lighting/lighting_maps_exercise4.png" target="_blank">result</a>.</li>
    207   </ul> 
    208 </p>
    209          
    210 
    211     </div>
    212