Shading is the computation used in 3D graphics to determine how a surface appears to one's eye, given surface properties and an incident lighting environment. Here is a sketch of a simple shader. It handles specular and diffuse shading, texture mapping, and a checker-board effect.
Shade computes how one point on a surface looks to an eye, given a
Surface is a structure containing the
surface properties, typically color, transparency, shininess, and a
normal give the position and
orientation of the surface point.
Shade sums the contributions
from each of the lights. Each light reflects proportionally to a
power of the dot product of the eye and the reflected light.
(define (pass-checks? freq s t) (odd? (xor (mod (* s freq) 1)) (mod (* t freq) 1)))
(define (shade eye lights surface s t normal) (define (shade-one-light light) (let ((k (dot-product eye (reflect (direction light) normal))) (exp (roughness->exponent (roughness surface))) (diffuse-color (cond ((texture? surface) ...) ((and (checked? surface) (pass-checks? (check-frequency surface) s t)) (check-color surface)) (else (diffuse-color surface))))) (gen-* (color light) (gen-+ (gen-* (power exp k) (specular-color surface)) (gen-* k diffuse-color))))) (gen-+ (ambient surface) ; maybe inside lights? (reduce gen-+ (map shade-one-light lights))))
Though this code is concise and very abstract, it is cluttered with
vector arithmetic and structure references. The vector and color
arithmetic is done with generic procedures
dynamically test the types of their arguments.
When rendering animation,
surface will be
fixed for at least each complete frame. If we hold these arguments
static and use RTCG, then a loop over the points on a surface can use
this program instead:
(define (shade-specialized s t normal) (let* ((t0 (reflect constant-direction0 normal)) (t1 (reflect constant-direction1 normal)) (k (+ (* eye_0 t0_0) (* eye_1 t0_1) (* eye_2 t0_2))) (k2 (* k k)) (k4 (* k2 k2)) (k8 (* k4 k4)) (k16 (* k8 k8)) (k20 (* k16 k4)) (m (+ (* eye_0 t1_0) (* eye_1 t1_1) (* eye_2 t1_2))) (m2 (* m m)) (m4 (* m2 m2)) (m8 (* m4 m4)) (m16 (* m8 m8)) (m20 (* m16 m4)))
(color (+ a_0 (* Cl0_0 (+ (* k20 Cs_0) (* k d_0))) (* Cl1_0 (+ (* m20 Cs_0) (* m d_0)))) (+ a_1 (* Cl0_1 (+ (* k20 Cs_1) (* k d_1))) (* Cl1_1 (+ (* m20 Cs_1) (* m d_1)))) (+ a_2 (* Cl0_2 (+ (* k20 Cs_2) (* k d_2))) (* Cl1_2 (+ (* m20 Cs_2) (* m d_2)))))))
All tests, loops, and indirect calls have been eliminated; the remaining code can be scheduled more easily and will run faster. Thus we can more than win back the time spent compiling it in a wide range of pixel resolutions, scene complexities, and numbers of frames.
But a lisp-generating extension for this shader would be much harder to write than the interpreter given above. If this were C/DCG instead of lisp, then the generating extension would be quite tricky for the programmer.