Update - November 15

The biggest hurdle that I had to overcome since the last update was integrating my shadow map code with a rendering system capable of displaying the power plant at interactive frame rates. I am currently using a system written by Brian Salomon and Karl Hillesland. The first issue I ran into was rendering to a pbuffer. On the GeForce3, you can render to a pbuffer that can be bound to a texture. This is important for avoiding the expensive readback of the depth buffer. When I first tried using my code in the new rendering system I would not get any shadows. It turns out that all of the geometry was being drawn with vertex arrays. Vertex arrays use client state that wasn't shared between the window GL context and the pbuffer GL context. Rather than try to synchronize both contexts which would have been difficult given the program's current structure,  I decided to try to use the window's context for drawing to the pbuffer. That didn't work because in order to use render-to-texture you have to use a render-to-texture compatible pixel format. The application window was not created with such a pixel format so its context could not be used to render to the pbuffer. In the end I decided to just do a readback for the present time and worry about setting up the render-to-texture later.

I wanted to have better control over the lighting. The GL pipeline gives you black shadows. I wanted to be able to use a shadow lightness setting to allow some of the light to remain unblocked so that details might still be seen even in the shadows. In order to have better control over the lighting, I decided to use a vertex program written in Cg together with register combiners. I set up the shader to handle a point light that casts shadow, a directional fill light, and an optional spot-light orginating at the head. When I tried to use the vertex program I ran into another problem. I set up my vertex program on the outside of the rendering loop. I found out that another vertex program was being used to inside the loop for occlusion culling. I modified that vertex program so that it would not use the parameters that I was using for mine and made sure that occlusion culling saved and restored the previously active vertex program. That still didn't fix the problem. Many hours later I noticed that vertex attribute arrays were enabled in the rendering code and never disabled. This had been the cause of all my woes.

I also found a bug in my perspective shadow map implemenation that was causing the shadows to get messed up when the light source became inverted in post-perspective space. One of the biggest problems with shadow maps is getting the right bias. Ideally you would like to vary the bias depending on the depth-slope of the polygons. You need a higher bias in areas of high depth slope to avoid surface self-shadowing effects. The bias gets wrapped up in the texture matrix when doing hardware texture mapping so there is no way to vary it per pixel. What you can do though is use PolygonOffset() to render the shadowmap. In effect this "bakes" the per pixel bias into the shadow map. When the light source becomes inverted you have to invert the polygon offset as well. I forgot to do this which caused the problems that I described in the previous update.


Here are some images of the partially loaded powerplant model:
No shadows
Completely black shadows
50 % blocked light shadows

Shadow with no bias
Shadows with slight bias

Future Direction

I still have to drop in the code for perspective shadow maps. The power plant has much deeper perspective than the toy scene I was using to test the perspective shadow maps. I hope to be able to see greater benefit from using them in the power plant model. There are significant aliasing artifacts with the normal shadow maps.

I am currently integrating the shadow mapping code with SWITCH, a multi-GPU occlusion culling solution. I intend to extend SWITCH to perform occlusion culling from the light source. This will enable me to use dynamic lighting on the Double Eagle oil tanker at a decent frame rate. The GPU that renders the visible geometry will also have to render the shadow map. This will drop the framerate by about a factor of 2 or 3 depending on how much geometry needs to be rendered for the shadow map. One solution to this problem is to use a separate GPU for rendering the shadow map. Since the shadow map is large, it must stay on the GPU. The solution is introduce an extra frame of latency in order to render the shadow map a frame before it is actually used. Two GPUs would alternate, one rendering the visible geometry, while the other renders the shadow map. Compositing hardware can be used to direct the output from both CPUs to a single monitor.

I would also like to implement shadow volumes. The occlusion culling can be used to cull the number of shadow volumes necessary to render a particular view.