i
i
i
i
i
i
i
i
5.5. Shading 115
Figure 5.15. Per-vertex evaluation of shading equations can cause artifacts that depend
on the vertex tessellation. The spheres contain (left to right) 256, 1024, and 16,384
triangles.
The arguments marked as uniform are constant over the entire model.
The other arguments (p and n) vary per pixel or per vertex, depending on
which shader calls this function. The saturate intrinsic function returns
its argument clamped between 0 and 1. In this case we just need it clamped
to 0, but the argument is known not to exceed 1, and saturate is faster
than the more general max function on most hardware. The normalize
intrinsic function divides the vector passed to it by its own length, returning
a unit-length vector.
So which frequency of evaluation should we use when calling the
Shade() function? When vertex normals are used, per-primitive evaluation
of the shading equation (often called flat shading) is usually undesirable,
since it results in a faceted look, rather than the desired smooth appear-
ance (see the left image of Figure 5.17). Per-vertex evaluation followed by
linear interpolation of the result is commonly called Gouraud shading [435].
In a Gouraud (pronounced guh-row) shading implementation, the vertex
shader would pass the world-space vertex normal and position to Shade()
(first ensuring the normal is of length 1), and then write the result to an
interpolated value. The pixel shader would take the interpolated value and
directly write it to the output. Gouraud shading can produce reasonable
results for matte surfaces, but for highly specular surfaces it may produce
artifacts, as seen in Figure 5.15 and the middle image of Figure 5.17.
These artifacts are caused by the linear interpolation of nonlinear light-
ing values. This is why the artifacts are most noticeable in the specular
highlight, where the lighting variation is most nonlinear.
At the other extreme from Gouraud shading, we have full per-pixel eval-
uation of the shading equation. This is often called Phong shading [1014].
In this implementation, the vertex shader writes the world-space normals
and positions to interpolated values, which the pixel shader passes to
Shade(). The return value is written to the output. Note that even if