GLSL Programming/GLUT/Glossy Textures

Sun set with a specular highlight in the Pacific Ocean as seen from the International Space Station (ISS).

This tutorial covers per-pixel lighting of partially glossy, textured surfaces.

It combines the shader code of the tutorial on textured spheres and the tutorial on smooth specular highlights to compute per-pixel lighting with a material color for diffuse reflection that is determined by the RGB components of a texture and an intensity of the specular reflection that is determined by the A component of the same texture. If you haven't read the tutorial on textured spheres or the tutorial on smooth specular highlights, this would be a very good opportunity to read them.

Gloss MappingEdit

The tutorial on lighting textured surfaces introduced the concept of determining the material constant for the diffuse reflection by the RGB components of a texture image. Here we extend this technique and determine the strength of thea specular reflection by the A (alpha) component of the same texture image. Using only one texture offers a significant performance advantage, in particular because an RGBA texture lookup is under certain circumstances just as expensive as an RGB texture lookup.

If the “gloss” of a texture image (i.e. the strength of the specular reflection) is encoded in the A (alpha) component of an RGBA texture image, we can simply multiply the material constant for the specular reflection k_\text{specular} with the alpha component of the texture image. k_\text{specular} was introduced in the tutorial on specular highlights and appears in the specular reflection term of the Phong reflection model:

I_\text{specular} = I_\text{incoming}\,k_\text{specular} \max(0, \mathbf{R}\cdot \mathbf{V})^{n_\text{shininess}}

If multiplied with the alpha component of the texture image, this term reaches its maximum (i.e. the surface is glossy) where alpha is 1, and it is 0 (i.e. the surface is not glossy at all) where alpha is 0.

Map of the Earth with transparent water, i.e. the alpha component is 0 for water and 1 for land.

Shader Code for Per-Pixel LightingEdit

The shader code is a combination of the per-pixel lighting from the tutorial on smooth specular highlights and the texturing from the tutorial on textured spheres. Similarly to the tutorial on lighting textured surfaces, the RGB components of the texture color in textureColor is multiplied to the ambient and diffuse lighting.

In the particular texture image to the left, the alpha component is 0 for water and 1 for land. However, it should be the water that is glossy and the land that isn't. Thus, with this particular image, we should multiply the specular material color with (1.0 - textureColor.a). On the other hand, usual gloss maps would require a multiplication with textureColor.a. (Note how easy it is to make this kind of changes to a shader program.)

The vertex shader is then:

attribute vec3 v_coord;
attribute vec3 v_normal;
varying vec4 position;  // position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;  // surface normal vector in world space
varying vec4 texCoords; // the texture coordinates
uniform mat4 m, v, p;
uniform mat3 m_3x3_inv_transp;
 
void main()
{
  vec4 v_coord4 = vec4(v_coord, 1.0);
  mat4 mvp = p*v*m;
  position = m * v_coord4;
  varyingNormalDirection = normalize(m_3x3_inv_transp * v_normal);
 
  texCoords = v_coord4;
  gl_Position = mvp * v_coord4;
}

And the fragment shader becomes:

varying vec4 position;  // position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;  // surface normal vector in world space
varying vec4 texCoords; // the texture coordinates
uniform mat4 m, v, p;
uniform mat4 v_inv;
uniform sampler2D mytexture;
 
struct lightSource
{
  vec4 position;
  vec4 diffuse;
  vec4 specular;
  float constantAttenuation, linearAttenuation, quadraticAttenuation;
  float spotCutoff, spotExponent;
  vec3 spotDirection;
};
lightSource light0 = lightSource(
  vec4(0.0,  1.0,  0.0, 1.0),
  vec4(1.0,  1.0,  1.0, 1.0),
  vec4(1.0,  1.0,  1.0, 1.0),
  0.0, 1.0, 0.0,
  180.0, 0.0,
  vec3(0.0, 0.0, 0.0)
);
vec4 scene_ambient = vec4(0.2, 0.2, 0.2, 1.0);
 
struct material
{
  vec4 ambient;
  vec4 diffuse;
  vec4 specular;
  float shininess;
};
material frontMaterial = material(
  vec4(0.2, 0.2, 0.2, 1.0),
  vec4(1.0, 0.8, 0.8, 1.0),
  vec4(1.0, 1.0, 1.0, 1.0),
  5.0
);
 
void main()
{
  vec3 normalDirection = normalize(varyingNormalDirection);
  vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - position));
  vec3 lightDirection;
  float attenuation;
 
  vec2 longitudeLatitude = vec2((atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
                                (asin(texCoords.z) / 3.1415926 + 0.5));
  // unusual processing of texture coordinates
 
  vec4 textureColor = texture2D(mytexture, longitudeLatitude);
 
  if (0.0 == light0.position.w) // directional light?
    {
      attenuation = 1.0; // no attenuation
      lightDirection = normalize(vec3(light0.position));
    } 
  else // point light or spotlight (or other kind of light) 
    {
      vec3 positionToLightSource = vec3(light0.position - position);
      float distance = length(positionToLightSource);
      lightDirection = normalize(positionToLightSource);
      attenuation = 1.0 / (light0.constantAttenuation
                           + light0.linearAttenuation * distance
                           + light0.quadraticAttenuation * distance * distance);
 
      if (light0.spotCutoff <= 90.0) // spotlight?
        {
          float clampedCosine = max(0.0, dot(-lightDirection, light0.spotDirection));
          if (clampedCosine < cos(radians(light0.spotCutoff))) // outside of spotlight cone?
            {
              attenuation = 0.0;
            }
          else
            {
              attenuation = attenuation * pow(clampedCosine, light0.spotExponent);   
            }
        }
    }
 
  vec3 ambientLighting = vec3(scene_ambient) * vec3(textureColor);
 
  vec3 diffuseReflection = attenuation 
    * vec3(light0.diffuse) * vec3(textureColor)
    * max(0.0, dot(normalDirection, lightDirection));
 
  vec3 specularReflection;
  if (dot(normalDirection, lightDirection) < 0.0) // light source on the wrong side?
    {
      specularReflection = vec3(0.0, 0.0, 0.0); // no specular reflection
    }
  else // light source on the right side
    {
        specularReflection = attenuation * vec3(light0.specular) * vec3(frontMaterial.specular) * (1.0 - textureColor.a)
          // for usual gloss maps: "* textureColor.a"
        * pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), frontMaterial.shininess);
    }
 
  gl_FragColor = vec4(ambientLighting + diffuseReflection + specularReflection, 1.0);
}

The texture and sphere have to be set up as described in the tutorial on textured spheres.

A useful modification of this shader for the particular texture image above, would be to set the diffuse material color to a dark blue where the alpha component is 0.

Shader Code for Per-Vertex LightingEdit

As discussed in the tutorial on smooth specular highlights, specular highlights are usually not rendered very well with per-vertex lighting. Sometimes, however, there is no choice because of performance limitations. In order to include gloss mapping in the shader code of the tutorial on lighting textured surfaces, the fragment shader should be replaced with this code:

varying vec3 diffuseColor;
    // the interpolated diffuse Phong lighting
varying vec3 specularColor;
    // the interpolated specular Phong lighting
varying vec4 texCoords;
    // the interpolated texture coordinates
uniform sampler2D mytexture;
 
void main(void)
{
    vec2 longitudeLatitude = vec2((atan(texCoords.y, texCoords.x) / 3.1415926 + 1.0) * 0.5,
                                  (asin(texCoords.z) / 3.1415926 + 0.5));
    // unusual processing of texture coordinates
 
    vec4 textureColor = 
        texture2D(mytexture, longitudeLatitude);
    gl_FragColor = vec4(diffuseColor * vec3(textureColor)
        + specularColor * (1.0 - textureColor.a), 1.0);
}

Note that a usual gloss map would require a multiplication with textureColor.a instead of (1.0 - textureColor.a).

SummaryEdit

Congratulations! You finished an important tutorial about gloss mapping. We have looked at:

  • What gloss mapping is.
  • How to implement it for per-pixel lighting.
  • How to implement it for per-vertex lighting.

Further ReadingEdit

If you still want to learn more


< GLSL Programming/GLUT

Unless stated otherwise, all example source code on this page is granted to the public domain.
Back to OpenGL Programming - Lighting section Back to GLSL Programming - GLUT section
Last modified on 20 May 2012, at 16:28