## Let There Be Shadows

March 12, 2007

New York, N.Y.

I want to have an example of rendering shadows in my WPF 3D book, and I've been exploring some techniques.

This one, however, I'm going to have to classify as a failure. You can try it out here:

I don't want to make the source code available because it's rather sloppy and way too specific to this particular program, but I can describe how it works:

The "chopper" is just two 8-vertex boxes stuck together and moved around with three animations: rotating around its axis, revolving around the Y axis, and moving up and down.

The "ground" is a plane surface composed of 200 by 200 rectangles, or 80,000 triangles. Interestingly, even without the shadow logic, increasing the number of triangles in the ground made the animation of the chopper increasingly choppy.

The illumination is a combination of *AmbientLight* with a color of 40-40-40 and *DirectionalLight* with a color of C0-C0-C0 and a direction of **(2, -3, -1)**.

I render the shadow by manipulating the *Normals* collection of the ground during a *CompositionTarget.Rendering* event. For each vertex on the ground, I set the vertex normal as follows: If no shadow is to appear at a vertex, I set the normal to the vector **(0, 1, 0)**, which is the default straight-up surface normal for the floor. For a shadow, I set the normal to **(3, 2, -1)**, which is at right angles to the directional light. The setting of this normal eliminates the directional light but not the ambient light from the shadow.

Originally, I called the 3D version of *VisualTreeHelper.HitTest* for every vertex on the floor, using a *RayHitTestParameters* object based on the vertex and the negative *Direction* vector of the *DirectionalLight*. If the ray from the floor in the negative direction of the directional light hit the chopper, then that vertex was part of the shadow. At that point I could set the vertex normal.

I then realized I could reduce the calls to *VisualTreeHelper.HitTest* considerably by first determining a 2D bounding box for the shadow. I did this by looking at rays from the vertices of the chopper — and there are only 16 of those — to the floor using the positive *Direction* vector of the *DirectionalLight*. Because this particular hit-test calculation only involves an intersection of a line and a plane, I did it in code rather than calling *VisualTreeHelper.HitTest*. This bounding box establishes minimum and maximum X and Z coordinates on the floor within which the shadow appears. The calls to *VisualTreeHelper.HitTest* are then restricted to that box.

I thought this was an interesting approach, and I was disappointed it didn't work better. The jaggies on the shadow can be eliminated only by increasing the resolution of the floor, but, of course, that tends to slow down the whole show.