GLSL Programming/GLUT/Multiple Lights

This tutorial covers lighting by multiple light sources.

Multiple subway lights of limited range in a tunnel.

This tutorial is an extension of the tutorial on smooth specular highlights. If you haven't read that tutorial, you should read it first.

Multiple Lights

edit

In the previous tutorials on diffuse reflection, etc., we have only used one light source, which was specified by the variable light0. However, we could handle an array of light sources.

Here, we take a certain number of these lights into account and add their contributions to the total lighting. This can be computed with a for-loop in the fragment shader of the tutorial on smooth specular highlights. The structure is this:

            const int numberOfLights = 8;
            ...
            // initialize total lighting with ambient lighting
            vec3 totalLighting = vec3(scene_ambient) * vec3(frontMaterial.ambient);
     
            for (int index = 0; index < numberOfLights; index++) // for all light sources
            {
               ... compute diffuseReflection and specularReflection for lights[index] ...

               totalLighting = totalLighting + diffuseReflection + specularReflection;
            }
            gl_FragColor = vec4(totalLighting, 1.0);
            ...

The total lighting by all lights is accumulated in totalLighting by initializing it to the ambient lighting and then adding the diffuse and specular reflection of each light to the previous value of totalLighting at the end of the for-loop. A for-loop should be familiar to any C/C++/Java/JavaScript programmer. Note that for-loops in GLSL are usually severely limited. Often (e.g. in OpenGL ES 2.0) the limits (here: 0 and numberOfLights) have to be constants, i.e. you cannot even use uniforms to determine the limits. (The technical reason for the limitation is that the limits have to be known at compile time in order to “un-roll” the loop.)

Complete Shader Code

edit

The vertex shader is the same as in the tutorial on smooth specular highlights:

attribute vec4 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
uniform mat4 m, v, p;
uniform mat3 m_3x3_inv_transp;

void main()
{
  position = m * v_coord;
  varyingNormalDirection = normalize(m_3x3_inv_transp * v_normal);

  mat4 mvp = p*v*m;
  gl_Position = mvp * v_coord;
}

The only tricky thing about the fragment shader is that the nesting of loops and conditionals appears to be limited on some GPUs and/or graphics drivers. In fact, I had to reduce the nesting by avoiding if-else-statements in order to compile the shader on my computer (the OpenGL API reported an internal error). In principle, nesting of conditionals and loops is allowed in GLSL; however, it is probably a good idea to test shaders that make extensive use of nested loops and conditionals on several systems.

varying vec4 position;  // position of the vertex (and fragment) in world space
varying vec3 varyingNormalDirection;  // surface normal vector in world space
uniform mat4 m, v, p;
uniform mat4 v_inv;

struct lightSource
{
  vec4 position;
  vec4 diffuse;
  vec4 specular;
  float constantAttenuation, linearAttenuation, quadraticAttenuation;
  float spotCutoff, spotExponent;
  vec3 spotDirection;
};
const int numberOfLights = 2;
lightSource lights[numberOfLights];
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)
);
lightSource light1 = lightSource(
    vec4(0.0, -2.0,  0.0, 1.0),
    vec4(2.0,  0.0,  0.0, 1.0),
    vec4(0.1,  0.1,  0.1, 1.0),
    0.0, 1.0, 0.0,
    80.0, 10.0,
    vec3(0.0, 1.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()
{
  lights[0] = light0;
  lights[1] = light1;

  vec3 normalDirection = normalize(varyingNormalDirection);
  vec3 viewDirection = normalize(vec3(v_inv * vec4(0.0, 0.0, 0.0, 1.0) - position));
  vec3 lightDirection;
  float attenuation;
  
  // initialize total lighting with ambient lighting
  vec3 totalLighting = vec3(scene_ambient) * vec3(frontMaterial.ambient);
  
  for (int index = 0; index < numberOfLights; index++) // for all light sources
    {
      if (0.0 == lights[index].position.w) // directional light?
	{
	  attenuation = 1.0; // no attenuation
	  lightDirection = normalize(vec3(lights[index].position));
	} 
      else // point light or spotlight (or other kind of light) 
	{
	  vec3 positionToLightSource = vec3(lights[index].position - position);
	  float distance = length(positionToLightSource);
	  lightDirection = normalize(positionToLightSource);
	  attenuation = 1.0 / (lights[index].constantAttenuation
			       + lights[index].linearAttenuation * distance
			       + lights[index].quadraticAttenuation * distance * distance);
	  
	  if (lights[index].spotCutoff <= 90.0) // spotlight?
	    {
	      float clampedCosine = max(0.0, dot(-lightDirection, normalize(lights[index].spotDirection)));
	      if (clampedCosine < cos(radians(lights[index].spotCutoff))) // outside of spotlight cone?
		{
		  attenuation = 0.0;
		}
	      else
		{
		  attenuation = attenuation * pow(clampedCosine, lights[index].spotExponent);   
		}
	    }
	}
      
      vec3 diffuseReflection = attenuation 
	* vec3(lights[index].diffuse) * vec3(frontMaterial.diffuse)
	* 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(lights[index].specular) * vec3(frontMaterial.specular) 
	    * pow(max(0.0, dot(reflect(-lightDirection, normalDirection), viewDirection)), frontMaterial.shininess);
	}

      totalLighting = totalLighting + diffuseReflection + specularReflection;
    }
  
  gl_FragColor = vec4(totalLighting, 1.0);
}

Summary

edit

Congratulations, you have reached the end of this tutorial. We have seen:

  • How a for-loop can be used in GLSL to compute the lighting of multiple lights in a fragment shader.

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