10.7 - Light Attenuation

A basic property of light is that it loses its intensity the further it travels from its source. That is why Venus and Mercury are much hotter than the Earth and Mars is much colder. The intensity of light from the sun changes in proportion its distance from the sun. The technical name for this is light attenuation.

This lesson explains how to include light attenuation in a lighting model.

Light Attenuation

Light becomes weaker the further is travels from its source. In the physical world the attenuation is proportional to 1/d2, where d is the distance between the light source and a surface. Using the function 1/d2 causes light to decrease very rapidly and so it is common for CGI applications to make attenuation be proportional to 1/d. Notice that if d is greater than 1, both equations calculate a fraction between 0.0 and 1.0. Attenuation simply calculates a percentage of the original light that is used to color a pixel.

In the original OpenGL lighting model, the equation 1.0/(c1 + c2*d + c3*d^2) was used to give programmers control over attenuation. You could set the values for c1, c2, and c3 to create a large number of possible attenuation functions. Since programmable shaders were introduced, you can implement any attenuation function that meets your application’s needs.

In the literature you typically see an attenuation equation like 10.0 / d written with the proportional constant in the denominator like this, 1.0 / 0.1*d. You get the same results with either equation, but perhaps one of the equations is more intuitive to you.

If you don’t want the attenuation to abruptly “kick in” at some distance from the light source, you can add a one to the denominator to guarantee that the denominator is always larger than the numerator. You can experiment below with various values for the constants c1 and c2 in the plot of the attenuation function 1.0/(1.0 + c1*d + c2*d^2). In many scenarios, setting c1 to 0.1 and c2 to 0.0 gives good results.


attenuation = 1.0 / (1.0 + 1.00*d + 1.00*d2
c1: 0.0 3.0
c2: 0.0 3.0

Example WebGL Program

Experiment with the following WebGL program by varying the constants in the attenuation function 1.0/(1.0 + c1*d + c2*d^2).

Experiment with Light Attenuation

Virtual World  Ambient, Diffuse, Specular w/ Attenuation
Please use a browser that supports "canvas"   Please use a browser that supports "canvas"
camera eye (0.0, 0.0, 5.0) camera center (0.0, 0.0, 0.0)
X: -5.0 +5.0 X: -5.0 +5.0
Y: -5.0 +5.0 Y: -5.0 +5.0
Z: -5.0 +5.0 Z: -5.0 +5.0
light position(3.0, 0.0, 5.0) light color (1.00, 1.00, 1.00)
X: -5.0 +5.0 Red: 0.0 1.0
Y: -5.0 +5.0 Green: 0.0 1.0
Z: -5.0 +5.0 Blue: 0.0 1.0
ambient intensities
(0.30, 0.30, 0.30)
attenuation
1.0/(1.0 + 0.10*d + 0.00*d^2)
Red: 0.0 1.0 c1: 0.0 3.0
Green: 0.0 1.0 c2: 0.0 3.0
Blue: 0.0 1.0
Change all intensities at once.
shininess = 30.0
0.1 128.0
Red Cube
Red X
Green Cube
Green Y
Blue Cube
Blue Z
White Cube
Change the shininess of all models.

Open this webgl demo program in a new tab or window

Please insure you make the following observations about attenuation:

  • Setting c1 to 0.0 and c2 to 1.0 is the attenuation for real world lighting: e.g., 1/d^2. However, this typically makes a CGI scene too dark.
  • Setting c1 to 0.1 and c2 to 0.0 gives good visual results for the example scene above.

Fragment Shader

The following fragment shader program implements light attenuation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// Fragment shader program
precision mediump int;
precision mediump float;

// Light model
uniform vec3  u_Light_position;
uniform vec3  u_Light_color;
uniform float u_Shininess;
uniform vec3  u_Ambient_intensities;
uniform float u_c1, u_c2;  // Attenuation constants: 1/(1 + c1*d + c2*d^2)

// Data coming from the vertex shader
varying vec3 v_Vertex;
varying vec4 v_Color;
varying vec3 v_Normal;

void main() {

  vec3 ambient_color;
  vec3 specular_color;
  vec3 diffuse_color;
  vec3 to_light;
  float distance_from_light;
  vec3 fragment_normal;
  vec3 reflection;
  vec3 to_camera;
  float cos_angle;
  float attenuation;
  vec3 color;

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // AMBIENT calculations
  ambient_color = u_Ambient_intensities * vec3(v_Color);

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // General calculations needed for both specular and diffuse lighting

  // Calculate a vector from the fragment location to the light source
  to_light = u_Light_position - v_Vertex;
  distance_from_light = length( to_light);
  to_light = normalize( to_light );

  // The fragment's normal vector is being interpolated across the
  // geometric primitive which can make it un-normalized. So normalize it.
  fragment_normal = normalize( v_Normal);

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // DIFFUSE  calculations

  // Calculate the cosine of the angle between the vertex's normal
  // vector and the vector going to the light.
  cos_angle = dot(fragment_normal, to_light);
  cos_angle = clamp(cos_angle, 0.0, 1.0);

  // Scale the color of this fragment based on its angle to the light.
  diffuse_color = vec3(v_Color) * u_Light_color * cos_angle;

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // SPECULAR  calculations

  // Calculate the reflection vector
  reflection = 2.0 * dot(fragment_normal,to_light) * fragment_normal
             - to_light;
  reflection = normalize( reflection );

  // Calculate a vector from the fragment location to the camera.
  // The camera is at the origin, so just negate the fragment location
  to_camera = -1.0 * v_Vertex;
  to_camera = normalize( to_camera );

  // Calculate the cosine of the angle between the reflection vector
  // and the vector going to the camera.
  cos_angle = dot(reflection, to_camera);
  cos_angle = clamp(cos_angle, 0.0, 1.0);
  cos_angle = pow(cos_angle, u_Shininess);

  // If this fragment gets a specular reflection, use the light's color,
  // otherwise use the objects's color
  specular_color = u_Light_color * cos_angle;

  attenuation = 1.0/
    (1.0 + u_c1*distance_from_light + u_c2*pow(distance_from_light,2.0));

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // COMBINED light model
  color = ambient_color + attenuation*(diffuse_color + specular_color);
  color = clamp(color, 0.0, 1.0);

  gl_FragColor = vec4(color, v_Color.a);
}
Line(s) Description
10 The constants for the attenuation function are declared as uniform variables so they can be modified by the example WebGL program. Typically you would just hard code appropriate constants in the equation used in lines 81-82.
40 The length of the “to the light” vector gives the distance between the surface and the light source. The length must be calculated before the vector is normalized.
81-82 The attenuation percentage is calculated.
86 The color of the fragment is determined by adding the ambient, diffuse and specular colors. The ambient light comes from unknown light sources and since the distance to those light sources is not known, the ambient light should not be attenuated. Therefore, only the diffuse and specular light is attenuated.

Summary

Have you noticed that all lighting and color calculations are percentages!

Glossary

attenuation
The decrease in intensity of electromagnetic wave energy as it travels away from its generating source.

Self Assessment

    Q-231: The visual effect of light attenuation is to make objects further from a light source to …

  • be darker.
  • Correct. Because the objects are receiving less light.
  • be brighter.
  • Incorrect. The exact opposite is true.
  • have more contrast.
  • Incorrect. They get less light, which decreases contrast.
  • have more detail.
  • Incorrect. They get less light, which decreases visual detail.

    Q-232: In the real world, what is the formula for calculating light attenuation, where d is the distance a surface is from a light source?

  • 1/(d^2)
  • Correct. One divided by the distance squared.
  • 1/d
  • Incorrect.
  • 1.0/(1 + d + d^2)
  • Incorrect.
  • 20/d
  • Incorrect.

    Q-233: CGI scenes often get better results from which attenuation formula below?

  • proportional to 1/d
  • Correct. Proportional to one divided by the distance.
  • 1/(d^2)
  • Incorrect.
  • 1.0/(1 + d + d^2)
  • Incorrect.
  • 20/d
  • Incorrect.

    Q-234: Using a combined light model, which of the following types of light should be attenuated? (Select all that apply.)

  • ambient light
  • Incorrect. Ambient light has no known source, so there is no “distance to the light” to allow for attenuation.
  • diffuse light from a point light source
  • Correct. The distance between the light source and the surface is known.
  • specular light from a point light source
  • Correct. The distance between the light source and the surface is known.
  • sun light
  • Incorrect. If the sun is being modeled, it is so far away from the scene that all objects whould have the same attenuation.
Next Section - 10.8 - Multiple Light Sources