GLSL Programming/GLUT/Lighting Textured Surfaces

This tutorial covers per-vertex lighting of textured surfaces.

Earthrise as seen from Apollo 8.

It combines the shader code of the tutorial on textured spheres and the tutorial on specular highlights to compute lighting with a diffuse material color determined by a texture. If you haven't read the tutorial on textured spheres or the tutorial on specular highlights, this would be a very good opportunity to read them.

Texturing and Diffuse Per-Vertex Lighting edit

In the tutorial on textured spheres, the texture color was used as output of the fragment shader. However, it is also possible to use the texture color as any of the parameters in lighting computations, in particular the material constant   for diffuse reflection, which was introduced in the tutorial on diffuse reflection. It appears in the diffuse part of the Phong reflection model:

 

where this equation is used with different material constants for the three color components red, green, and blue. By using a texture to determine these material constants, they can vary over the surface.

Shader Code edit

In comparison to the per-vertex lighting in the tutorial on specular highlights, the vertex shader here computes two varying colors: diffuseColor is multiplied with the texture color in the fragment shader and specularColor is just the specular term, which shouldn't be multiplied with the texture color. This makes perfect sense but for historically reasons (i.e. older graphics hardware that was less capable) this is sometimes referred to as “separate specular color”.

attribute vec3 v_coord;
attribute vec3 v_normal;
uniform mat4 m, v, p;
uniform mat3 m_3x3_inv_transp;
uniform mat4 v_inv;

varying vec3 diffuseColor;
  // the diffuse Phong lighting computed in the vertex shader
varying vec3 specularColor;
  // the specular Phong lighting computed in the vertex shader
varying vec4 texCoords; // the texture coordinates

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,  2.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 mymaterial = 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(void)
{
  vec4 v_coord4 = vec4(v_coord, 1.0);
  mat4 mvp = p*v*m;
  vec3 normalDirection = normalize(m_3x3_inv_transp * v_normal);
  vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - m * v_coord4));
  vec3 lightDirection;
  float attenuation;

  if (light0.position.w == 0.0) // directional light
    {
      attenuation = 1.0; // no attenuation
      lightDirection = normalize(vec3(light0.position));
    }
  else // point or spot light (or other kind of light)
    {
      vec3 vertexToLightSource = vec3(light0.position - m * v_coord4);
      float distance = length(vertexToLightSource);
      lightDirection = normalize(vertexToLightSource);
      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, normalize(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);
    // without material color!

  vec3 diffuseReflection = attenuation
    * vec3(light0.diffuse)
    * max(0.0, dot(normalDirection, lightDirection));
    // without material color!

  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(mymaterial.specular)
        * pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)),
              mymaterial.shininess);
    }

  diffuseColor = ambientLighting + diffuseReflection;
  specularColor = specularReflection;
  texCoords = v_coord4;
  gl_Position = mvp * v_coord4;
}

And the fragment shader modulates the diffuseColor with the texture color and adds the specularColor:

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

    gl_FragColor = vec4(diffuseColor
        * vec3(texture2D(mytexture, longitudeLatitude))
        + specularColor, 1.0);
}

In order to assign a texture image to this shader, you should follow the steps discussed in the tutorial on textured spheres.

Summary edit

Congratulations, you have reached the end. We have looked at:

  • How texturing and per-vertex lighting are usually combined.
  • What a “separate specular color” is.

Further Reading edit

If you still want to know 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