w00t

22/05/09
Cascaded Shadows Maps

Finally got around to playing with this feature, another one that's been on the to do list for years.



So, with a simple shadow map, you record the depth as seen from the light; when you render the scene you also compute the distance to the light from where you are, and compare that with the shadow map. If you're further, then there's something up there closer to the light than you, so you're in shadow. Pretty simple. The problem that Cascaded Shadow Maps, or CSM solve, is that if you have a realistic scene (ie. it's not very tiny), then you need a huge shadowmap to get any kind of decent resolution. With CSM, you split up the scene the camera sees in a number of chunks, and you set up a shadow map for every chunk. The further you get away from the camera, the larger the area that one map covers.

Using 3 such maps, you'd get something like this shot; red objects are covered by a map that's close but only covers a few objects, green is further out, blue is even further.. and beyond that is the no-shadow region.

20090521-csm_maps.jpg

In the upper right, the yellow stuff shows the 3 shadow/depth maps as seen from above/the sun.

Zooming out shows the area covered by every shadow map. Since every map is the same resolution (1024x1024 in this case), you can see that shadows will be blockier as objects are further away from the camera, but that's ok, perspective correction will make'em smaller again :)

20090521-csm6b.jpg

Freezing the "camera" and looking at a closeup of the shadow of the lanterns, you can clearly see the effect of the reduced resolution as we move further away from where we originally were:

Up close and personal:
20090521-csm_lantern_high.jpg

Into the green zone:
20090521-csm_lantern_med.jpg

Into the blue zone:
20090521-csm_lantern_low.jpg

Now that's all fine and handy but positioning these boxes on the fly is tricky; in particular, small rotations of the camera will cause the texels to scroll over your landscape and this may give rise to all sorts of artifacts and flickering, due to point sampling a moving texture. A simple solution is to make sure that the area covered by each map remains constant. Normally, you would tightly pack the shadow map around the section of frustum it covers to maximize the amount of texels per square meter. But this means that if the camera tilts down, you suddenly have a lot less ground to cover, yielding many more texels per unit, which is good by itself -- except that this density change represents a sampling frequency change, and that's asking for trouble. So you pick one size and stick with that, accepting that you're trading some potential quality improvement for visual stability. This fixed size needs to be large enough to encompass any "cross view" of the frustum, so you can figure out its bounding sphere (a la Killzone), its AABB (a la DICE), or whatever.

Another thing that changes your sampling pattern is movements of the camera. This can be handled easily enough by just snapping your texture sampling to fixed world coordinates. In other words, don't drag the shadow map along when the camera moves until the camera has covered enough space that the whole setup can jump ahead a full texel (in either u or v).


Something that's not clear from the usual slides on this topic is that even with these two fixes in place, the boundary between the individual levels will still move. Suppose you compute a Killzone-sphere around a section of frustum. Unless you center this sphere on the camera, a rotation of the camera will move this sphere through space. Therefor objects that fell in this sphere in one frame, may now shift to another (closer or further) section in the next frame. So depending on how things are set up, there can still be some resolution jumping as the frustum changes. Centering the bounding volumes on the camera seems out of the question, because then you waste 1-(fov/360) of your shadow map looking straight ahead (ie 75% for fov90, ouch). So, maybe that's obvious to everybody else but it hadn't occurred to me before :)


I experimented with the "DICE-technique" from GDC2009. Normally you bind every shadow map as a rendertarget, cull objects for it, and draw. Thus N slices increase your drawcall count by O(N). If you're using instanced rendering, this can be avoided using still just one draw call (per object type). First, all shadow map rendertargets are put in a Texture Array. The idea is to draw all objects together, regardless of which frustum they belong to, and have the geometry shader move the object to the appropriate map. If an object overlaps multiple frustums, you just put it several times in the instance list, every time with a different frustum tag/id. This works, except that on my ATI-4870 it divides the framerate by 10. I'm already using instancing so that can't be it, indeed, just removing the GS brings FPS back up... Maybe I'm doing something wrong, but just updating the drivers "improved" things, now the framerate is only divided by 6. Again, maybe it's me... maybe it's the driver. So for now I'm not doing this GS-based collapsing, it's just N loops through the data for now.


...for the astute observers: the reason, by the way, that the low-res screenshot above gives such smooth "blobs" instead of the typical blocky staircasing, is that I'm also using Variance Shadow Maps. Will write a couple of things about that, too, later on :)
posted at 06:14:32 on 05/22/09 by peirz - Category: zwans - Tag: programming / rendering / graphics

Comments

BELGIUM peirz wrote:

... 500th post smile
05/22/09 06:22:06
 

Comment Notification

get a mail...
manage...

Add Comments