Multiple Lights

In which you learn how to illuminate your models using more than one light source.

Imagine a game in which the player is running down a hallway. A series of light fixtures are inset in the ceiling. A painting on the wall may be illuminated by several of these light fixtures at the same time. The fragment shaders you've seen so far only consider a single light source.

When paint mixes together, it gets darker. When light mixes together, it gets brighter. Accordingly, to shade a fragment with multiple light sources, you calculates its diffuse and specular terms with respect to each individual light source and then add them together.

This fragment shader computes the diffuse terms for two light sources:

uniform vec3 lightEyePositions[2];
uniform vec3 diffuseColors[2];
uniform vec3 albedo;

in vec3 mixNormal;
in vec3 mixPosition;

out vec4 fragmentColor;

void main() {
  vec3 normal = normalize(mixNormal);
  vec3 diffuse = vec3(0.0);

  for (int i = 0; i < 2; ++i) {
    vec3 lightDirection = normalize(lightEyePositions[i] - mixPosition);
    float litness = max(0.0, dot(normal, lightDirection));
    diffuse += litness * albedo * diffuseColors[i];

  fragmentColor = vec4(diffuse, 1.0);

The lights' positions and colors are sent in as elements in an array of uniforms. To upload individual elements in an array of uniforms from JavaScript, you include the index in the uniform's name:

shaderProgram.setUniform3fv('diffuseColors[0]', diffuseColors[0]);
shaderProgram.setUniform3fv('diffuseColors[1]', diffuseColors[1]);
shaderProgram.setUniform3fv('lightEyePositions[0]', lightEyePositions[0]);
shaderProgram.setUniform3fv('lightEyePositions[1]', lightEyePositions[1]);

The fragment shader above is used in this renderer to illuminate the torus in both orange and blue lights:

Theoretically you could add as many lights as you need. However, if a scene has a large number of lights, the fragment shader will run slowly. Game developers avoid these costs by precomputing as many of the lighting calculations as possible. The results are stored or baked into images, which are pasted onto the surfaces.