CS 248 Marc Levoy
Introduction to Computer Graphics Spring, 1995
Stanford University
Shading computations for project #4
1. Local versus infinite light sources (a reiteration of the May 30 lecture)
Composer offers two kinds of light sources: directional and point. A point
light, sometimes called a local light, means that the light lies within (or
nearby) the scene. A desk lamp is a local light. The location of a local
light is specified by Composer as a 3D point S in world space. By contrast, a
directional light, sometimes called an infinite light, means that the light is
infinitely far away. The sun is an infinite light. The location of an
infinite light is specified by Composer as a vector pointing from
the light towards the scene.
The advantage of local lights is that they give sharp highlights on flat
surfaces, while infinite lights do not. The advantage of infinite lights is
that they simplify shading calculations. In order to implement a local light,
the lighting vector L used in the Phong illumination equation (16.20 in the
textbook) must be recomputed at each polygon vertex from the light location S
and the vertex location P. By contrast, in order to implement an infinite
light, the L used in the Phong equation is simply set equal to the negative of
the vector provided by Composer, and no per-vertex calculation of a lighting
vector is necessary.
2. Local versus infinite viewers
By analogy to local and infinite lights, there are also local and infinite
viewers. A local viewer means that the viewer for the purposes of shading lies
within (or nearby) the scene. The location of a local viewer is specified by a
3D point E in world space. The obvious place for a local viewer is at the same
place as the viewer used in constructing the perspective matrix. In the
formulation used in the textbook (equation 6.48), this is at the origin. By
contrast, an infinite viewer means that the viewer for shading purposes is
considered to be infinitely far away. In this case, it is specified by a
direction V rather than by a location. The obvious direction for an infinite
viewer in our perspective formulation is [0 0 1 0] transpose in eye space, i.e.
pointing back from the scene towards the origin but parallel to the Z-axis.
While Composer does not support selection of local versus infinite viewer (it
implements only an infinite viewer), the underlying Graphics Library (GL) does.
It is important not to confuse the viewer used in shading with the viewer used
in constructing the perspective matrix. Given the definition above, it is
possible to have an infinite viewer for the purposes of shading and a local
viewer in the perspective matrix. Does this situation make physical sense? Of
course not, but in the realm of graphics hacks and of practical workstations,
there are reasons for it. The advantage of a local viewer for shading purposes
is that it yields sharp highlights on flat surfaces. The advantage of an
infinite viewer is that it simplifies shading calculations. As in the case of
local lighting, in order to implement a local viewer, the viewer vector V used
in the Phong illumination equation must be recomputed for each polygon vertex
from the viewer location E and the vertex location P. By contrast, in order to
implement an infinite viewer, its specification V is the same V that is used in
the Phong equation; no per-vertex calculation of a viewer vector is necessary.
To summarize, a local light gives better shading than an infinite light. The
same applies for local viewers. In particular, flat surfaces (i.e. those that
are not part of a mesh and/or do not have interpolated normals) exhibit a
specular highlight if a local light or viewer are used, but not if an infinite
light and viewer are used. A curved surface, including curved surfaces
approximated by triangle meshes, will exhibit a specular highlight even if an
infinite light source and viewer are used; they simply won't look as sharp.
3. Interpolated shading for a local light and local viewer
To help you get started on project #4, I summarize in these next two sections
the shading calculations you need to implement. In the interests of
completeness and in order to serve as a reference for other renderers you may
write in your long and illustrious careers, I first give the calculations
needed to implement a local light source and viewer. I then describe how to
simplify the calculations in order to implement an infinite light source and
infinite viewer.
Definitions and symbols:
Rx(thetax) = X-rotation matrix
Ry(thetay) = Y-rotation matrix
Rz(thetaz) = Z-rotation matrix
T(dx,dy,dz) = translation matrix
persp = transpose of SGI persp matrix from Haeberli & Akeley
M1 o M2 = composition of any matrices M1 and M2
(implemented by premultiplication, i.e. M2 M1)
M^ = the matrix M-hat
M(-1) = inverse of M for any matrix M
Pv,Pv' = a vertex in world, screen space
P = a sample point in world space
Nv = a vertex normal in world space
Np,N = unnormalized,normalized pixel normal in world space
S = world space location of the local light source
E = world space coordinates of the viewer eyepoint
X.Y = dot product of any two vectors X and Y
(X.Y)^n = dot product raised to the power n
|X| = the L-2 norm (i.e. length) of a vector X
[dx dy dz] = a 3-vector of increments in a scan-converter
world space = the coordinate system given to you by Composer
eye space = world space rotated and translated for viewing,
but w/o any perspective mapping
screen space = world space rotated, translated, and perspective
mapped, a.k.a. clipping space
In the pseudocode that follows, assume that all coordinates are 4-vectors
and that all matrices are 4x4. In the section entitled "Reducing the
dimensionality", I suggest how to improve on this.
For each view of the scene:
Compute the compound viewing transformation matrix with perspective:
M <-- Rx(thetax) o Ry(thetay) o Rz(thetaz) o T(Tx,Ty,Tz-d1)
o persp o S(1,1,-1/2) o T(0,0,-1/2)
Compute another compound viewing matrix but without the perspective:
M^ <-- Rx(thetax) o Ry(thetay) o Rz(thetaz) o T(Tx,Ty,Tz-d1)
Compute the world space location of the local viewer:
E <-- M^(-1) ([0 0 0 1] transpose)
where [0 0 0 1] transpose is the eye space location of the viewer,
at the origin.
For each triangle in the scene:
For each vertex in the triangle:
Transform its coordinates from world space to screen space:
Pv' <-- M Pv
Clip to viewing frustum. If outside frustum, discard triangle and loop
Divide by W as in assignment #3, then perform back-face culling.
For each sample position in the triangle (using your scan converter):
Interpolate Np from the Nv 's at the vertices
(incrementally, as you now do for z') and normalize it:
Np <-- Np + [dNx, dNy, dNz] transpose
N <-- Np / |Np|
Interpolate P similarly from the world space Pv 's at the vertices:
P <-- P + [dPx, dPy, dPz] transpose
Compute normalized vectors pointing toward the light and viewer:
L <-- (S-P) / |S-P|
V <-- (E-P) / |E-P|
Compute the reflection vector (see p. 730 in the text):
R = 2N(N.L)-L
Compute shading (eqn. 16.20, page 734 in the text).
I = Ia ka Od + fatt Ip [ kd Od (N.L) + ksOs (R.V)^n ]
Note on reducing the dimensionality of the matrices and vectors:
If you study where zeros fall in matrices M^, you will realize
that M^(-1) can be reduced (after inversion) to a 3x4. In
addition, vectors Nv, Np, N, L, V, and R can all be 3-vectors.
Further optimizations:
If you study the code above, you will realize that some steps
(e.g. the increments) should be performed at every sample
position in a triangle regardless of visibility, but that some
steps (e.g. the normalizations and shading calculations)
should only be performed at sample positions that pass the
Z-buffer test, i.e. that are found to be in front of all other
triangles encountered so far at this sample position.
Construct your code carefully with respect to this issue.
4. Implementing an infinite light and infinite viewer
So that's the story for a local light source and local viewer. To implement an
infinite light and infinite viewer, a user-specified S and E are not used.
Instead, L and V are treated as the same at all pixels. In your case, Composer
provides you with -L (in world space) for each directional light. I strongly
suggest that you set the eye space direction of the infinite viewer to [0 0 1
0] transpose. You will have to compute the world space direction of the
infinite view yourself as M^(-1) [0 0 1 0] transpose. If you study the
placement of zeros in this matrix multiplication, you will realize that you
only need the upper-left 3x3 submatrix of M^, in which case M^(-1) is simply
the inverse of the rotations.
If you examine the above code, you will also realize that for this case you
won't need to interpolate P and you won't need to recompute L or V at each
pixel. Study the code and work out the details for yourself.
To gain even more speed, you can replace (R.V)^n (and the calculation of R.V)
with (N.H)^n where H = (L+V)/|L+V| assuming normalized L and V. H is the
direction of maximum highlight, also called the halfway vector. This
alternative method of computing highlights is described in your textbook on
page 731. Note that this method is only advantageous if L and V are not
recomputed per vertex, which is precisely the case with an infinite light and
infinite viewer.
P.S. User feedback for a local light
If you implement a local light source, and if the light source is placed within
the viewing frustum, it would be nice (and would help you debug your code) if
you displayed a small icon at the light source location, i.e. at the point S,
just as Composer does using a white sphere.