10.3 - Smooth “Phong” Shading

An easy way to change the colors across the surface of a triangle is to interpolate between the normal vectors defined at a triangle’s vertices. This is known as Phong shading and is named after its creator, Bui Tuong Phong.

This lesson describes how Phong shading can be used to make triangles simulate curved surfaces.

Surface Normal Vectors


The normal vector of a triangle / flat shading

A triangle defines a surface that lies in a plane. There are an infinite number of vectors that lie in the triangle’s plane, but there are only two vectors that are perpendicular to the triangle’s plane. By convention, one of these vectors defines the “front side” of the triangle, and the other vector defines the “back side” of the triangle. These are referred to as the “front face” and “back face” of the triangle. If the vector, n, defines the “front face”, then -n defines the “back face”.

If we store a single normal vector for the entire triangle, and that vector is used for the light reflection calculations for every fragment that composes the triangle, then the entire triangle will have the same color. We refer to this as “flat shading”.


Intermediate normal vectors

If we store a different normal vector for each vertex of a triangle, then the normal vector at each fragment can be an interpolated vector across the surface of the triangle. Since the light reflection calculations are based on the surface normal direction, each fragment can potentially have a different color assigned to it. We are simulating a curved surface, as shown by the orange dashed lines in the diagram to the right.

The term “normal vector” might be confusing because we are now defining a vector in a direction that is not perpendicular to the triangle’s plane. You might think that we would call the vector something else, like “light reflection reference vector”, but we don’t. A surface normal vector defines how light reflects off of a surface, regardless of whether it is perpendicular to the triangle’s plane.

We discussed interpolation in lesson 7.6. Remember that interpolation is performed using parametric equations. Suppose we have two normal vectors, n1 and n2. You can calculate intermediate vectors by varying a parametric parameter, t, between 0.0 and 1.0 like this:

n = (1-t)*n1 + t*n2  // 0.0 <= t <= 1.0

Interpolated normal vectors

Since vectors have 3 components, <dx,dy,dz>, this equation actually represents three equations:

  • ndx = (1-t)*n1dx + t*n2dx
  • ndy = (1-t)*n1dy + t*n2dy
  • ndz = (1-t)*n1dz + t*n2dz

You don’t have to implement these equations; the WebGL graphics pipeline automatically interpolates vectors that are stored in varying variables. But it is important that you understand how the interpolation works because the interpolation modifies the magnitude of the intermediate vectors and they are no longer normalized, even if the original vectors were normalized. This is illustrated by the example interpolation in the diagram. When you use an interpolated normal vector in a fragment shader you must always normalize it first.

Calculating Smooth Normal Vectors


Average normal vector

Smooth shading across adjacent triangles of a model is accomplished by setting the normal vector of a vertex to an average of the face normal vectors of the triangles that use that vertex. For example, in the diagram to the right a vertex is used by five triangles, so the normal vector of the center vertex is set to an average of the five normal vectors from the adjacent triangles. You can calculate the average normal vector like you calculate any average value: for each component of the vectors, <dx,dy,dz>, sum each component and divide by the number of vectors. The resulting vector will be pointing in the correct direction to simulate a smooth transition between the adjacent triangles, but it will not necessarily be normalized. Therefore, it should be normalized.


Duplicate normal vectors

The situation becomes more complex if your model contains triangles that share a vertex and lie in the same plane. Consider a simple cube like the one shown in wireframe mode to the right. It is composed of 6 sides that are defined by 12 triangles (two triangles per side). In the diagram you can see that the vertex at the red dot is part of 4 triangles, but just 3 cube faces. If you take the average of the normal vectors of the 4 triangles that share the vertex, the normal vector will be skewed towards the right side that has 2 triangles that use the vertex. Therefore, when you gather the face normal vectors to calculate an average, you must test the face normal vectors to make sure they are unique. If you find multiple normal vectors that are identical, then you assume that they all define the same surface plane and you only include one of them in your average calculation.

The JavaScript class createModelsFromOBJ in the file learn_webgl_obj_to_arrays.js calculates a “smooth normal vector” for each vertex of a model if “smooth mode” is enabled for the faces of the model. If you want smooth normal vectors for a model, make sure there is a line in the OBJ file, above the face definitions, that enables “smooth mode.” The “switch” that enables “smooth mode” looks like s on. For example,

s on
f 9//1 8//1 23//1 24//1
f 2//2 1//2 16//2 17//2


Phong shading
Create a unique normal vector for each fragment that composes a triangle by interpolating the normal vectors defined at the triangle’s vertices.
Next Section - 10.4 - Texture Mapping Using Images