10.6 - Combining Ambient, Diffuse, and Specular Lighting¶
Now that you understand the three basic lighting models, ie. ambient, diffuse, and specular, let’s combine them into a general model for lighting a scene. This is straightforward because light is additive. The calculations we have discussed in the previous lessons can be used in a single shader program and the resulting colors simply added together.
An Ambient, Diffuse, and Specular Lighting Model¶
For a point light source that is inside a scene, we need the following data to model the light:
- The light’s position
- The light’s color
There is typically some light in a scene that is not associated with any particular light source:
- The ambient percentages for background lighting
Light interacts with the surfaces of an object. To model lighting effects we also need basic properties of the surfaces, such as:
- The surface’s color
- The surface’s location
- The surface’s orientation (its normal vector)
- The surface’s shininess exponent
Shaders: Uniforms vs. Attributes
If all surfaces of a model have the same shininess, then the
shininess exponent should be stored as a single uniform
variable of the model. However, if some triangles of a model have
different shininess, then an array of shininess values, one for
each vertex, would be stored in a GPU buffer object and accessed
using an attribute
variable.
The Math for a Combined Lighting Model¶
There is no new mathematics in this lesson. The ambient, diffuse, and specular light calculations are performed as we discussed in the previous lessons and the resulting colors are added together to get the final fragment color.
A WebGL Demo Program¶
Experiment with the following WebGL program. There are many values to experiment with, so please take your time and discover how the properties interact with each other.
Experiment with a Combined Lighting Model
Virtual World Ambient, Diffuse, & Specular lightingcamera 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) |
|
Red: | 0.0 1.0 |
Green: | 0.0 1.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
As you experiment with the demonstration program, please make sure you observe the following characteristics of combining ambient, diffuse, and specular lighting.
- If no direct light is shining on a face, the face’s color is based solely
on the ambient light calculation.
- If the light source does not reflect into the camera, the colors are
based on the sum of the ambient and diffuse calculations.
- If the light source does reflect into the camera, the colors are based on the sum of the ambient, diffuse, and specular calculations.
Shader Programs for a Combined Lighting Model¶
Please study the following shader programs using the notes below as your guide.
Vertex Shader¶
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 | // Vertex Shader
precision mediump int;
precision mediump float;
// Scene transformations
uniform mat4 u_To_clipping_space; // Projection, camera, model transform
uniform mat4 u_To_camera_space; // Camera, model transform
// Light model
uniform vec3 u_Light_position;
uniform vec3 u_Light_color;
uniform float u_Shininess;
uniform vec3 u_Ambient_intensities;
// Original model data
attribute vec3 a_Vertex;
attribute vec3 a_Color;
attribute vec3 a_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-camera transformations on the vertex and pass this
// location to the fragment shader.
v_Vertex = vec3( u_To_camera_space * vec4(a_Vertex, 1.0) );
// Perform the model-camera transformations on the vertex's normal vector
// and pass this normal vector to the fragment shader.
v_Normal = vec3( u_To_camera_space * vec4(a_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 graphics pipeline.
gl_Position = u_To_clipping_space * vec4(a_Vertex, 1.0);
}
|
Line(s) | Description |
---|---|
9-13 | The light model is defined by four values: u_Light_position ,
u_Light_color , u_Shininess , u_Ambient_intensities . |
Fragment Shader¶
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 | // 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;
// 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;
vec3 fragment_normal;
vec3 reflection;
vec3 to_camera;
float cos_angle;
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;
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;
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// COMBINED light model
color = ambient_color + diffuse_color + specular_color;
color = clamp(color, 0.0, 1.0);
gl_FragColor = vec4(color, v_Color.a);
}
|
Line(s) | Description |
---|---|
36-41 | The to_light and fragment_normal vectors are used
by both the diffuse and specular calculations. They should be calculated
only once. |
48-52 | This code can be written in less lines if you combine the operations into single statements. For example, the diffuse calculation could be written like this in a single line: diffuse_color = vec3(v_Color) * u_Light_color * clamp(dot(vertex_normal, to_light), 0.0, 1.0);
You are encouraged to NOT write complex statements like this until much later in your learning. The GLSL compiler will optimize your code, so write your code as clearly as possible for humans! Use descriptive variable names and multiple, distinct statements for clarity. |
79-80 | The final color of the fragment is the sum of the ambient, diffuse and
specular calculations. This is a component-wise vector addition.
That is, if a = <a0,a1,a2> and b = <b0,b1,b2> , then
a + b is equal to a 3-component vector
<a0+b0, a1+b1, a2+b2> . It is possible that the calculated color values
become greater than 1.0, which means there is more than 100% of the
color – which is not possible. Therefore, the values are clamped to 1.0
as the maximum possible value. |
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.
Self Assessment¶
- The color of a surface is the result of adding the reflected color from each light source (plus the ambient light in the scene).
- Correct.
- You can take the average location of all lights in a scene and calculate the reflected light from that average location.
- Incorrect. Not even close!
- You can add all lights in a scene to get the total light.
- Incorrect. You add the reflected light from each light source, not the lights themselves.
- Lights can be added together to create one “super light”.
- Incorrect.
Q-228: What does “light is additive” mean?
- Ambient light.
- Yes.
- Diffuse light.
- Yes.
- Specular light.
- Yes.
- Sun light.
- Incorrect.
Q-229: A “combined light model” creates the color a fragment by adding which of the following together? (Select all that apply.)
- If a color component of fragment’s color becomes more than 100%, it makes the color be 100%.
- Correct.
- It makes each color component either 0.0 or 1.0.
- Incorrect. 0.0 and 1.0 are the min and max values, respectively. If the value is between 0.0 and 1.0, the value remains unchanged.
- It sets each color component to an average of the three values.
- Incorrect. Nope!
- It makes the color values more homogeneous.
- Incorrect. That’s silly!
Q-230: What is the purpose for the shader program statement,
color = clamp(color, 0.0, 1.0);
?