PSM Notes & 2D Viz App

Thatcher Ulrich http://tulrich.com

Collected the notes 31 May 2003 from earlier emails sent to Jon Blow & cbloom & some other people. Jon has some really good ideas that we'll hopefully hear more about if they work out.

[there are some later notes at http://tulrich.com/geekstuff/psm_notes2.html ]

--- an optimistic email, w/ a viewer app ---

I've been trying to get a better intuitive handle on the sampling issues with PSM. My mental visualizations just weren't cutting it, so I cooked up a 2D visualization demo which may be of interest to you guys, it's at:

http://tulrich.com/geekstuff/images/psm_vistool.zip

The .exe prints some short usage when you run it.

[btw the source is in the zip and can easily be compiled for Linux]

[see below for links to screenshots]

[...]

The basic idea behind the demo is that the yellow lines are rays from an infinite directional light source. The lines are spaced linearly in perspective space; i.e. the same way the pixels would be distributed in a perspective shadow map [not exactly the same, but close -tu 5/31/03]. So imagine those yellow lines intersecting a frame buffer somewhere.

The blue circles are occluders, and the white trapezoid is the frustum. So basically I have code in there that determines the boundary of the PSM, such that the PSM will always be able to see a shadow cast from any occluder that might intersect the frustum; i.e. it will sample all possibly-visible shadows. This is roughly what I'd do in a real app.

[...]

I remain hopeful about PSM; I think it might still be great for character-only shadows on Xbox, assuming the fade-out thing can be made to work, and assuming it's possible to make a good framing algorithm.

--- later, a pessimistic email ---

Now, unfortunately I think PSM may be DOA. First, look at this screenshot:

http://tulrich.com/geekstuff/images/psm_great.png

This is the ideal case for PSM. The light is coming in from the side. You can see from the density of the yellow lines that we have tons of resolution in the parts of the shadow map where it will map onto the scene near the viewpoint. We have very little resolution in the shadow map in the parts that will map onto the far-away parts of the scene. The 1/z resolution density perfectly matches the 1/z of the view perspective. All is wonderful; we can draw pixel-perfect shadows of everything in the world.

Next shot:

http://tulrich.com/geekstuff/images/psm_ok.png

This is an "ok" case for PSM. It's a bit tilted, so the matchup between view perspective and shadow-map density is not perfect, but we're still generally getting more resolution where we need it, close to the viewpoint. If we let our occluders cast longer shadows though, you could imagine an occluder in the upper left (where there's low PSM resolution) casting a very long shadow that gets fairly close to the viewer.

Now the killer:

http://tulrich.com/geekstuff/images/psm_disaster.png

Here the light is directly behind the viewer. Because the rays from a directional light source must be parallel, we can't change the sampling density of the PSM in the y-direction (i.e. in the direction of the light). So the sampling density directly in front of the viewer must be the same sampling density all the way at the far plane.

This is by no means an uncommon case -- think of an outdoor scene with a follow-cam, where the sun is somewhat low in the sky; the player should be able to get the sun directly behind the camera without much trouble.

Now, if you forced the sun to be more or less directly overhead, and generally kept the camera pointing more or less horizontal, then you could stay in the "ok to great" range, and then maybe PSMs are still good. You can degrade smoothly to crappy PSM resolution if you don't want to have hard constraints on the camera; maybe the user won't notice that the shadows go to hell when they tilt the camera way up.

Other ideas:

Actually, it's conceivable that we could adjust the sampling density of the "disaster" case to look something like this:

http://tulrich.com/geekstuff/images/psm_double_maps_idea.png

I'm pretty confident we can't do that with a single 4x4 matrix though, but maybe by having two PSMs it could be done.

So, I think at the very least, you need more than one PSM to make it workable in a general way.

--- back to cautious optimism ---

But I'm back to being a little optimistic. Why not force the shadow-casting light to be very high in the sky? Nothing says it has to exactly match the lights used for other shading. So then for follow-cam-like situations, the bad cases are when the camera is:

Hacky and app-specific, but it might still be good. It does rule out some of the more dramatic uses of character shadows though.

--- a quick note on an OK way to construct light frustum ---

1. decide on the "view_proj_prime" matrix. This can just be your normal rendering transform to go from world-space to screen-space, but sometimes you will want to change the viewing parameters so that the "virtual" viewpoint is behind all the potential casters, etc. I'm not sure yet what a good recipe for this is.

2. figure out where your light is in screen-space, and make a transformation matrix Vl that maps from screen-space into light-view space. The way I do this is to point the light at the viewpoint (defining the direction), then project the corners of the view frustum into screen space, project those points onto a plane whose normal is the light-view-dir, find the tightest AABB around that set of planar points, and choose an up vector that aligns with this box. So you have the light pos, the light-view-dir, and an up vector, which is enough to make Vl.

3. Figure out your Pl matrix (light projection), that tightly fits the stuff you need in your shadow map. To do this, take extremal points from all potential casters (I use the verts of their AABB's) and push them through Vl * view_proj_prime. Then project them onto the z=1 plane. Make an AABB around this set of points. Do the same thing with the view-frustum extremal points, making an AABB. Find the intersection of these two AABBs. That gives you the desired bounds of your projection; plug the box dimensions into glFrustum or D3DXPerspectiveOffCenterXHBlahBlahBlah.

[implemented all this; the whole thing kinda works but still some glitches, that may be due to other bugs]

Let me know if you have a better way to do this that's practical.