9.3 - Specular Lighting

This lesson discusses how to implement specular reflection of light.

A Specular Lighting Model

../_images/specular_vector.png

Specular light reflection.

A light ray that reflects off of a surface at an equal but opposite angle to its incoming angle is called “specular reflection.” We assume that the amount of light reflection is equal to the intensity of the incoming light. That is, we assume that the magnitude of the reflection vector is equal to the magnitude of the incoming light vector. The reflection ray is indicated by the green vector in the diagram to the right. Please study the diagram carefully.

If a specular reflection light ray goes straight into a camera, it is as if the camera is seeing the light source directly, even though it has bounced off of an object. The camera is seeing the light of the light source, not the color of the object. If you have a white light source, the specular reflection will be white. If you have a red light source, the specular reflection will be red. Therefore, to model specular reflection you need to specify the color of the light source in your lighting model.

../_images/specular_scattering.png

Specular light scattering.

We assume there is a small amount of light scattering around the reflection vector, but not much. The scattering is centered around the reflection vector as shown in the diagram to the left.

../_images/specular_angle.png

Specular light intensity.

If we calculate a vector from a fragment to the camera, and then calculate the angle between it and the reflection vector, we can use the angle to estimate the amount of specular reflection the camera receives. If the angle is zero the camera is receiving the entire reflection light ray. If the angle is very small, then some percentage of the reflection ray is going into the camera. If the angle is large, then no light from the reflection is entering the camera. If the angle is between small and large, we need a way to calculate some percentage of reflected light that is entering the camera. We used a cosine function to calculate diffuse light percentages, but the cosine function is too broad for the a focused specular reflection. However, if we raise the cosine function to a power, the curve collapses around the Y axis. Experiment with various exponents in the plot of the cos(angle)exp function below. By using various exponents for this equation, we can simulate various amounts of light scattering around the specular reflection vector. If the exponent is large, such as 100, then the cos(angle)exp shrinks around the Y axis and only very small angles will return a significant percentage value. If the exponent is small, such as 1.0, a broad amount of light around the reflection ray will be simulated.


cos(angle)1.0
0.1 128

The Math for Specular Reflection

To calculate specular reflection, we need two vectors:

  • A vector from the fragment location to the camera.
  • A reflected light vector

The angle between these two vectors determines the amount of specular reflection.

To calculate a vector from the fragment position to the camera, subtract the head of the vector (the camera) from the tail (the fragment location). As we previously discussed, we are performing these calculations in “camera space” and the camera is located at the global origin, (0,0,0). Therefore, a vector to the camera is:

to_camera[0] = 0 - fragment_position[0]
to_camera[1] = 0 - fragment_position[1]
to_camera[2] = 0 - fragment_position[2]

We can calculate this vector by simply multiplying the fragment location by -1.

Calculating the Reflection Vector

../_images/reflection_vectors1.png

Calculating the reflection vector

To calculate the reflection vector, we need to set up several intermediate vectors. Please study the diagram to the right before proceeding with this discussion. The position of the light source can be projected onto the vertex’s normal vector. This is the “p” vector in the diagram. It can be shown that a vector in the direction of the normal vector that has a length to the projected point can be calculated by taking the dot product of -L and a normalized vertex normal vector. The dot product calculates the angle between the two vectors, but only when the vectors have unit length. Notice that the normal vector is normalized, but the -L vector is not. This provides the correct length for the N vector to the projected point. To summarize, to calculate N we:

  1. Normalize the vertex’s normal vector to make it one unit in length. Let’s call it n.
  2. Calculate the dot product of -L and n. Let’s call this value s.
  3. Scale n by s to create the vector N, which is in the direction of the vertex’s normal vector and has a length to the projected point of the light source.
  4. From simple vector addition, L + N === P.
../_images/reflection_vectors2.png

Calculating the reflection vector

Now we know the vector P. Observe that from simple vector addition, the reflection vector R is equal to N + P. We can substitute the calculations for N and P into this equation like this:

R = N + P
R = n*dot_product(n,-L) + (L + N)
R = n*dot_product(n,-L) + (L + n*dot_product(n,-L))
R = 2*n*dot_product(n,-L) + L

If you flip the direction of L and follow the same logic, you get:

R = 2*n*dot_product(n,L) - L   // When L goes from the vertex to the light source

The Percentage of a Reflection Ray a Camera Receives

We have a vector from the vertex to the camera and we have a reflection vector. The angle between these two vectors indicates how much of the reflection ray goes into the camera. The cosine of this angle, which is a percentage between 0.0 and 1.0 is raised to some power to simulate a focused bean of light around the reflection ray. The exponent is typically called the “shininess” exponent. The larger the exponent, the more shiny an object appears. Shiny objects have small, focused specular reflection. Dull objects have large, spread out specular reflection.

A WebGL Demo Program for Specular Reflection

Experiment with the following WebGL program. Move both the light source and the camera to see the interaction between them. In your mind visually project the light source onto the object and its reflection into the camera. Does the specular reflection make sense? Please experiment with the program until it does.

Manipulate the position of a light source and a camera.

The left canvas shows the relative location of the light source, the camera, and an object.
The right canvas shows the scene from the camera's vantage point with the light source used to calculate specular reflection coloring.
Please use a browser that supports "canvas" Please use a browser that supports "canvas"
Manipulate the camera's location and center of view: Manipulate the light:
eye (0.0, 0.0, 5.0) center (0.0, 0.0, 0.0) light location (3.0, 3.0, 3.0)
X: -5.0 +5.0 X: -5.0 +5.0 X: -5.0 +5.0
Y: -5.0 +5.0 Y: -5.0 +5.0 Y: -5.0 +5.0
Z: -5.0 +5.0 Z: -5.0 +5.0 Z: -5.0 +5.0
Shininess = 30.0
0.1 128.0
Light color = (1.0, 1.0, 1.0)
Red: 0.0 1.0
Green: 0.0 1.0
Blue: 0.0 1.0

Open this webgl program in a new tab or window

As you experiment with the demonstration program, please make sure you observe the following characteristics of specular reflection.

  • The relative position of the object, the light source, and the camera impacts specular reflection.
  • The program is calculating the specular reflection in the fragment shader pixel by pixel.
  • Our simple light model does not account for light being blocked by other objects in the scene. If an object was blocking light from the cubes, you would still see specular reflection on the cubes.
  • The color of the specular reflection is the color of the light from the light source. At the locations where the light from the light source is reflected directly into the camera’s lens, the camera sees the light and not the color of the object.

Specular Reflection in Shader Programs

Please study the following shader programs. Then compare the programs to the comments below.

Vertex Shader

// Vertex Shader
precision mediump int;
precision mediump float;

// Scene transformations
uniform mat4 u_PVM_transform; // Projection, view, model transform
uniform mat4 u_VM_transform;  // View, model transform

// Light model
uniform vec3 u_Light_position;
uniform vec3 u_Light_color;
uniform float u_Shininess;

// Original model data
attribute vec3 a_Vertex;
attribute vec3 a_Color;
attribute vec3 a_Vertex_normal;

// Data (to be interpolated) that is passed on to the fragment shader
varying vec3 v_Vertex;
varying vec4 v_Color;
varying vec3 v_Normal;

void main() {

  // Perform the model and view transformations on the vertex and pass this
  // location to the fragment shader.
  v_Vertex = vec3( u_VM_transform * vec4(a_Vertex, 1.0) );

  // Perform the model and view transformations on the vertex's normal vector
  // and pass this normal vector to the fragment shader.
  v_Normal = vec3( u_VM_transform * vec4(a_Vertex_normal, 0.0) );

  // Pass the vertex's color to the fragment shader.
  v_Color = vec4(a_Color, 1.0);

  // Transform the location of the vertex for the rest of the graphics pipeline
  gl_Position = u_PVM_transform * vec4(a_Vertex, 1.0);
}
  • This is the same vertex shader program used for diffuse lighting in the previous lesson, with the addition of two new uniform variables: u_Light_color and u_Shininess. These values enhance our lighting model.
  • All calculations in the fragment shader will be done in “camera space,” so the vertex data is transformed by the model and camera transformations, but not the projection transformation.

Fragment Shader

The fragment shader calculates a reflection vector and then determines if any reflected light should be used to color the pixel.

// 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;

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

void main() {

  vec3 to_light;
  vec3 vertex_normal;
  vec3 reflection;
  vec3 to_camera;
  float cos_angle;
  vec3 specular_color;
  vec3 object_color;
  vec3 color;

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

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

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

  // Calculate a vector from the fragment location to the camera.
  // The camera is at the origin, so negating the vertex location gives the vector
  to_camera = -1.0 * v_Vertex;

  // Calculate the cosine of the angle between the reflection vector
  // and the vector going to the camera.
  reflection = normalize( reflection );
  to_camera = normalize( to_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;
  object_color = vec3(v_Color) * (1.0 - cos_angle);
  color = specular_color + object_color;

  gl_FragColor = vec4(color, v_Color.a);
}
  • The angle between a vector to the camera and a reflection vector is calculated. The calculations are exactly as described in the math section above.
  • It is important to note that the cosine of the angle is clamped to a value between 0.0 and 1.0 before is it raised to the shininess exponent. The angle can be negative if the reflection vector and the vector to the camera have an angle greater than 90 degrees. Raising a number to a power can make a value positive. For example, (-2)2 is +4. (If the cosine of the angle is negative, it will be clamped to zero, which means 0% of reflected light gets to the camera, which is the physical result we want.)
  • If the value of the cos(angle) is one, we want the color of the light source for this fragment. If the value of the cos(angle) is zero, we want the color of the object. If the cos(angle) is some percentage between zero and one, we take this percentage of the light’s color, and the left-over percentage, (1.0 - cos_angle), of the color of the object. “Color is additive” so we can simply add the two color values together.

Type of Light Source

The example WebGL program above was based on a point light source. If you had a different type of light source, such as a sun light source, the shader programs would have to be changed because the definition of your light source would change, but the fundamental math would be the same.

Glossary

specular reflection
Light from a light source reflects from the surface of an object directly into a camera.
shininess
The size of the specular highlight on an object. If an object is very smooth, the specular highlight will be small. For dull or rough objects, the size will be larger.
Next Section - 9.4 - Ambient Lighting