Scalar maps

As we have said earlier, technically textures are just huge tables of numbers, they don't need to encode visual colors. If a texture is used to encode something different, it's usually called a map.

The simplest of those are scalar maps, i.e. those which assign just a number to a certain pixel, not anything that has direction. Using the texture mapping of the base texture, by using maps we can define detailed variations of various surface properties across a triangle.

For instance, if we want to render Earth from space, all the oceans and rivers will show a bright specular reflection, whereas land usually will not - using a texture to encode what areas show specular reflection leads to the concept of a specular map.

We can likewise define several colors and use the color channels of a texture to determine where these colors are mixed with a base texture color, leading to the concept of a dirt map which can be used to render dynamical discolorations (e.g. soot from a badly working engine). Textures in combination with colors can also be used to determine lit areas, allowing to simulate light or shadow falling onto a surface, which brings us to lightmaps and shadow maps.

Usually such maps are supplied separately from the base texture layer, and this might be called an explicit map. However, sometimes there's a characteristic color in the base texture layer which marks an area. For instance, in the example of Earth seen from space above, it's only oceans and water which have a strong specular reflection. However, these areas usually show as a deep blue in the base texture layer - and there's nothing else that appears deep blue and reflects light on the globe. So rather than using an extera texture, the color of the base texture can be tagged to encode where strong reflection should be, and that is an example of an implicit map.

Since it has to do with pixels and texture coordinate lookups, mapping is almost exclusively the domain of the fragment shader.

Specular maps

If you read back, the basic specular lighting code in the Blinn-Phong scheme was:

if (gl_FrontMaterial.shininess > 0.0)
      {
      specular.rgb = (gl_FrontMaterial.specular.rgb
      * gl_LightSource[0].specular.rgb
      * pow(NdotHV, gl_FrontMaterial.shininess));
      }

Assume we'd like to replace the material-defined specular color and shininess by a specular map. Using a texture gives us four color channels (rgba) - we can use the first for the specular color and the alpha channel for shininess. Since shininess does not range from 0-1 like color values but from 0 to 128, we have to remember to multiply though, so the relevant bits of a specular map code might look like:

uniform sampler2D specularTex;

vec4 specTexel = texture2D(specularTex, gl_TexCoord[0].st);

vec3 specularColor = specTexel.rgb;
float shininess = specTexel.a * 128.0;

if (shininess > 0.0)
      {
      specular.rgb = (specularColor * gl_LightSource[0].specular.rgb
      * pow(NdotHV, shininess));
      }

And that's really all there is to it.

A multi-channel lightmap

Suppose we want to simulate cockpit lighting. There may be several lamps in the cockpit which can each illuminate an area of the panel, and their intensity can be dimmed. A multi-channel lightmap is the answer to this challenge.

The idea is to use each of the four color channels of the texture to mark the regions which are illuminated if the corresponding light is on (and how strong the illumination is), then multiply this value with the dimmer setting of the light and then multiply the intensity setting obtained thus with the light color vector - which then gets to multiply the surface color vector just as for standard Blinn-Phong.

For this, we pass four color vectors and four intensity settings as uniform data types into the fragment shader, as well as the lightmap texture itself. The relevant bits of the fragment shader might then look like

uniform sampler2D lightMapTex;

uniform vec3 lightmap_red;
uniform vec3 lightmap_green;
uniform vec3 lightmap_blue;
uniform vec3 lightmap_alpha;

uniform lightmap_factor_r;
uniform lightmap_factor_g;
uniform lightmap_factor_b;
uniform lightmap_factor_a;

vec4 lightmapTexel = texture2D(lightMapTex, gl_TexCoord[0].st);

vec4 lightMapIntensity = vec4 (lightmap_factor_r, lightmap_factor_g, lightmap_factor_b, lightmap_factor_a);
lightMapIntensity *=lightmapTexel;

vec3 lightMapColor = lightmap_red * lightMapIntensity.r
      + lightmap_green * lightMapIntensity.g
      + lightmap_blue * lightMapIntensity.b
      + lightmap_alpha * lightMapIntensity.a;

(...)

fragColor = max(fragColor.rgb, lightmapcolor * texel.rgb);

As the code snippet indicates, there's various possibilities how to merge a second light source into a Blinn-Phong scheme and we'll talk about this more later. How this should be done in detail is a question on what the philosophy of intensity modeling is for instance. Here we've just used a simple criterion to let the lightmap be visible only if the external illumination (assumed to be already done for fragColor is weaker than the lightmap.

A multi-channel lightmap used for the three sets of Shuttle payload bay floodlights.

An implicit lightmap

Suppose you have a cockpit instrument panel which is labeled in a distinctive color during the day. During the night, there's the option to add backlighting to the labels to make them visible. We could do this by supplying the above multi-channel lightmap which contains a duplicate set of the labels. But in fact we already have the labels in the base texture, so there's no need for this, we can simply instruct the shader that everything that is in the label color during the day is supposed to be illuminated at night.

For this to work, we need to specify the label color vector and a parameter for the tolerance (i.e. how close to the label color are we still going to assume we need illumination) in addition to the actual light color of the map we want to apply and the light intensity. The idea is then to do a simple comparison of color vectors to determine the lightmap strength.

In the following we determine closeness of colors by two 'distance' thresholds, a lower one to determine up to what difference we want full illumination and an upper one determining when we want no longer illumination. A code snippet might look as follows:

uniform vec3 lightMapColor;
uniform vec3 tagColor;

uniform float lightIntensity;
uniform float threshold_low;
uniform float threshold_high;

uniform sampler2D baseTex;

vec4 texel = texture2D(baseTex, gl_TexCoord[0].st);

float cdiff = (length(texel.rgb - tagColor));
float enhance = 1.0 - smoothstep(threshold_low, threshold_high, cdiff);

(...)

fragColor.rgb = fragColor.rgb + enhance * lightMapColor * lightIntensity;

(Note that in this shader the lightmap illumination is just added to the surface color, which is a different model of relative intensity).

An implicit lightmap used for instrument backlighting.

In general, an implicit lightmap can't do anything an explicit lightmap couldn't do - but it's much simpler and uses less resources. It saves having a whole texture in memory in the above example at the expense of five uniform float values. Some care has to be taken though that it doesn't capture accidentially parts of the base texture which should not be illuminated.

Continue with Relief maps.


Back to main index     Back to rendering     Back to GLSL

Created by Thorsten Renk 2016 - see the disclaimer and contact information.