Specular Term
Only rough surfaces can be rendered realistically with just diffuse and ambient terms. Very smooth surfaces do not diffuse light in all directions but reflect it in a concentrated direction. The concentrated light appears to the viewer as a shiny or specular highlight. The word specular means “reflecting as a mirror.” We render these highlights on our shaded surfaces by adding a specular term to our lighting equation.
When a ray of light reaches a smooth surface at at angle \(a\), it bounces off the surface at the same angle, but in a direction that is mirrored about the surface's normal:
If the viewer is aligned with the direction of reflection, they will see the highlight. As with the diffuse term, the cosine between the two normalized vectors measures their alignment, but it's faster to use the dot product:
Again, we don't want to allow the specularity factor to go below 0:
The specularity will be a proportion between 0 and 1. To control the size of the highlights, we raise this value to some power:
The shininess affects how quickly the curve drops to 0. The larger the number, the more quickly the specularity curve plummets and the smaller the highlight. Consider this profile of five different shininess values:
Observe how specular highlights appear on a torus in this renderer:
Try changing the shininess and the specular color.
Computing the specular term in GLSL is a bit more involved than the diffuse and ambient terms. The light direction is first flipped about the normal using the builtin reflect
function. It expects the incident vector to be pointing from the light source to the fragment, rather than from the fragment to the light source. The lightDirection
computed earlier must therefore be negated:
vec3 reflectDirection = reflect(-lightDirection, normal);
vec3 reflectDirection = reflect(-lightDirection, normal);
The eye direction is a vector from the fragment to the eye. Since we're lighting in eye space, the eye is at the origin, and this subtraction yields the eye direction:
vec3 eyeDirection = vec3(0.0) - mixPositionEye;
vec3 eyeDirection = vec3(0.0) - mixPositionEye;
Subtracting from the origin is the same as negating, so we simplify this statement:
vec3 eyeDirection = -mixPositionEye;
vec3 eyeDirection = -mixPositionEye;
Recall that the vectors must have unit length if we're going to use the dot product as a measure of their alignment. The reflect direction will already be normalized if the light direction is normalized. However, the eye direction must be normalized:
vec3 eyeDirection = normalize(-mixPositionEye);
vec3 eyeDirection = normalize(-mixPositionEye);
This fragment shader calculates the specular term and adds it to the ambient and diffuse terms calculated earlier:
uniform vec3 specularColor;
uniform float shininess;
// ...
void main() {
// ...
vec3 eyeDirection = normalize(-mixPositionEye);
vec3 reflectDirection = reflect(-lightDirection, normal);
float specularity = pow(max(0.0, dot(reflectDirection, eyeDirection)), shininess);
vec3 specular = specularity * specularColor;
vec3 rgb = ambient + diffuse + specular;
fragmentColor = vec4(rgb, 1.0);
}
uniform vec3 specularColor; uniform float shininess; // ... void main() { // ... vec3 eyeDirection = normalize(-mixPositionEye); vec3 reflectDirection = reflect(-lightDirection, normal); float specularity = pow(max(0.0, dot(reflectDirection, eyeDirection)), shininess); vec3 specular = specularity * specularColor; vec3 rgb = ambient + diffuse + specular; fragmentColor = vec4(rgb, 1.0); }
Note that the specular term doesn't consider the material's albedo as did the diffuse and ambient terms. Albedo describes how much light a surface absorbs and reflects. A material with specular highlights is very reflective and doesn't absorb much incoming light, so the albedo is ignored.
The combination of ambient, diffuse, and specular terms is called Phong illumination. This model was described in 1975 by graduate student Bui Tuong Phong at the University of Utah.
The Phong illumination model offers a plausible rendering of shaded surfaces, but it is not physically accurate. In particular, it cuts off the specular contribution when the angle between the reflection direction and eye direction is greater than 90 degrees. In certain situations, such as when the sun is setting over a body of water, this cutoff leads to an unrealistic lack of specular highlights.
Graphics researcher Jim Blinn tweaked Phong's model to compensate for these awkward angles. Blinn's model replaces the reflect direction with a vector that points halfway between the eye direction and the light direction. The degree of alignment between this half direction and the normal determines the specularity. This modification isn't grounded in physical reality any more than Phong's original model. In interactive computer graphics, real isn't nearly as important as fast and convincing.
This fragment shader uses the Blinn-Phong illumination model:
uniform vec3 specularColor;
uniform float shininess;
// ...
void main() {
// ...
vec3 eyeDirection = normalize(-mixPositionEye);
vec3 halfDirection = normalize(eyeDirection + lightDirection);
float specularity = pow(max(0.0, dot(halfDirection, normal)), shininess);
vec3 specular = specularity * specularColor;
vec3 rgb = ambient + diffuse + specular;
fragmentColor = vec4(rgb, 1.0);
}
uniform vec3 specularColor; uniform float shininess; // ... void main() { // ... vec3 eyeDirection = normalize(-mixPositionEye); vec3 halfDirection = normalize(eyeDirection + lightDirection); float specularity = pow(max(0.0, dot(halfDirection, normal)), shininess); vec3 specular = specularity * specularColor; vec3 rgb = ambient + diffuse + specular; fragmentColor = vec4(rgb, 1.0); }
The Blinn-Phong model is commonly used and was the default in OpenGL before programmable shaders were added. Now that shaders have been added, there is no default. You get to pick your lighting model. And by pick, we mean that you get to implement any lighting model you want.
This renderer renders the torus using the Blinn-Phong illumination model:
Contrast this renderer to the Phong renderer above. The highlights under the Blinn-Phong model tend to be bigger and, at extreme angles, more elliptical than circular.