PSM Notes 2 -- How to do them right

Thatcher Ulrich http://tulrich.com

2 Sep 2003 -- based on some email discussions with Jon Blow, Charles Bloom, Casey Muratori and Sean Barrett.

19 Sep 2003 -- On Sep 4 I got an email from Folker Schamel, who saw these notes and commented that he'd had the same idea a couple months ago. I didn't think of this until a couple weeks ago, so he definitely came up with it first. Also he made an excellent diagram. His original email to Bert Freudenberg (in German) is here: http://tulrich.com/geekstuff/schamel_on_psm.txt . The diagram is linked below. I don't read German, but the diagram pretty much says it all.

Earlier notes at http://tulrich.com/geekstuff/psm_notes.html

25 Sep 2003 -- There is a pretty comprehensive thread on gamedev.net which addresses all kinds of issues relating to this problem. Definitely worth a read: http://www.gamedev.net/community/forums/topic.asp?whichpage=1&pagesize=20&topic_id=179941

Problem -- PSM Doesn't Really Work

So the big hassle with PSM is that it only really works in some restricted cases, where the light is coming from the sides or above/below, and so occluders aren't casting big nasty shadows towards the viewpoint, or from behind the viewpoint into the scene, etc. Which makes it useful for some very specific applications (e.g. it's OK for character shadows in a 3rd-person follow-cam game), but practically useless for general-purpose full-scene shadowing.

However, after lots of experimenting, arguing and thinking, Jon and I have identified a method which should really work for arbitrary full-scene pixel-accurate hard shadows from a directional light. The nub is that you need to use up to 5 shadow maps, each with a different perspective transform, instead of just one.

BIG CAVEAT: we have not implemented this yet, only thought about it.

Initial Idea

The first cut at this is to divide up the scene into zones, where you use one shadow map for shadows near the viewpoint, another for mid-range shadows, and possibly more shadow maps for distant shadows. It's perhaps simplest to use ordinary orthographic shadow maps here. This is a big improvement in that it kinda works, and it's pretty easy to visualize and understand.

Better Idea

However, it doesn't really conquer some of the nasty cases, and it doesn't make optimal use of shadow buffer resolution. For example, one of the nasty cases is a distant tree with the sun behind it, casting a long shadow onto the ground in front of the viewer.

A better idea is to take the frustum, look at it from the point of view of the light, and cut the frustum into (up to 5) pieces. The pieces are determined by the faces of the frustum which are facing the light. (@@ expand on this)

E.g. to the light, the frustum might look like this:

(diagram @@ TODO make a real one)

----
     +--------------==+
     |\         ----  |
     | \     ---      |
     |	+---+         |
     |  +---+         |
     | /     ---      |
     |/         ----  |
     +--------------==+
----

Note that we can see five frustum faces. We extrude each of those faces in the light direction, far enough so that the extrusions enclose all of the frustum. Then we treat each of those extrusions as a PSM frustum. We compute the PSM transform so that the associated shadow map exactly covers the original frustum face. Note that generally, the face you start with is very warped when seen from the light, i.e. considering the right face from above, the light sees something like this:

----
                    --+
                ----  |
             ---      |
      	    +         |
            +         |
             ---      |
                ----  |
                    --+
----

We want to map that quadrilateral onto our rectangular shadow-buffer. It turns out that PSM frustums are perfect for this, and the necessary warping is exactly what we need to guarantee good resolution when projected back into the view frustum in view-space. I.e. we need more image resolution near the left edge of that face, where it's squashed in, and progressively less image resolution as we go out towards the right side which is stretched out.

We don't have any hassles with singularities when rendering occluders, because the z=0 plane is clipped right out using plain frustum culling when we render into the PSM.

(@@ TODO need a good diagram for this)

(@@ TODO figure out & show the math)

Here's a really nice 2D diagram of the idea, thanks to Folker Schamel: http://tulrich.com/geekstuff/images/psm_bert_diagram.gif

Tada!

So guess what? It all works out perfectly; you get more than enough shadow-buffer resolution everywhere, and you don't grossly oversize anything, and the perspective effect kicks in the way you want it to. You just have to query all five of those shadow buffers in order to decide if a pixel is in shadow. (You are free to do culling etc, but in general a single triangle can be in all five PSM frustums so the worst-case is five texture lookups.)

Limitations etc

This does NOT solve the problem where an oblique receiver stretches the shadow buffer resolution. Maybe some of the soft shadow hacks come to the rescue for this? (ala Chan & Durand or Wyman & Hansen)

How much depth resolution do we need? Could be an issue, since occluders can come from way outside the frustum; 16 bits is probably plenty though, and for distant occluders we can use tricks like flattening them onto the near-plane of the PSM frustum (an appealing idea for regular PSMs that doesn't actually work; i.e. it does help with the depth resolution, but not with the image resolution).

How does this extend to point lights?

Since this technique is conservative, can we now be liberal with our shadow-buffer sizing, and possibly save lots of RAM at the expense of some SB stretching on receivers that are very near the viewpoint?

How do we apply the shadow? One way is to set up 5 texture stages for the 5 buffers, and do a big AND in the pixel shader. If we're just dealing with one light though, there is a better way: cut the scene into 5 pieces, corresponding to the 5 shadow-frustum volumes, and draw each piece independently, applying only one PSM on each piece. There may be some interesting clipping issues, but at worst they can be handled using something like texgen + texkill (i.e. what NVIDIA uses for user-clipping planes in OpenGL). The pieces of the scene can overlap slightly, and that shouldn't cause any real problems.

As usual, alpha transparency is not handled in any convenient way by this technique.