XNA Light Pre-Pass: Cascade Shadow Maps

Hi folks!

After a long time without opening VisualStudio (I deserved a little vacation after DBP), I’m back! I’ve submited my game (far from what I expected it to be), here are some screenshots:

I would like to thanks Virginia Broering, Rudi Bravo, Rafael Moraes, Felipe Frango, Rodrigo Cox, Marina Benites, Fabiano Lima, and specially Justin Kwok that provided me his old Xbox to test the game.

On this post I will talk about my implementation of Cascade Shadow Maps, a common technique used to handle directional light shadows. Some good descriptions can be found here and here. As before, the full source code for this sample can be found here: use it at your own risk!!

The basic idea is to split the view frustum into smaller volumes (usually smaller frustums or frusta), and to generate a shadow map for each volume. This way, we can have a better distribution of our precious shadow map’s texels into each region: the volume closer to the camera is smaller than the farthest one, so we have more shadow resolution closer to the camera. The drawback is that we have more draw calls/state changes, since we render to more than one shadow map. We can use shadow map atlasing to optimize things up, we just need to create a big shadow map that fits all our smaller shadow maps and offset the texel fetch on pixel shader.

You should measure/change/measure your application to decide the best number of splits (or cascades) and the resolution of the shadow maps. I’m using 3 cascades, at 1024×1024 each on PC and 640×640 on Xbox. Also, it’s a good idea to limit it to the main/dominant/call_it_as_you_like directional light, since it’s a performance hungry feature.

The steps needed to have it working are:

  • At initialization, create a big render target that fits all 3 shadow maps (if you want to change the sample to use 2 or 4 or whatever, go ahead). In PC, we should have a 3072×1024 texture. The texture’s format should be SINGLE (floating point), with a depth-buffer attached, and as before, we use the DISCARD_CONTENTS flag. We output the depth to that texture, in LINEAR space.
  • For each frame:
    • Bind the render target and clear it to white, ie the farthest value we can have;
    • Compute the range of each sub-frustum. You can use just a linear distribution, but it won’t provide you the best resolution distribution. I’m using a quadratic distribution, so the first frustum is way smaller than the last one;
    • For each cascade:
      • Compute an orthographic camera that fits the sub-frustum and points in the light’s direction. In my sample I’m using the technique “Fit to scene” described on the link above, where each new sub-frustum overlaps the previous ones. This way we can use some tricks to avoid shadow jittering when the camera moves. I didn’t like the results I have, since we lose lots of resolution when we use that trick, so I left a boolean to trigger it on/off. Compute the viewProjection matrix for this camera;
      • Compute the correct viewport for this sub-frustum;
      • Draw each mesh that is inside this sub-frustum, using the same technique we already use for spot light shadows, but with this new viewProjection matrix.
    • When rendering the directional light that has this cascade shadow map, choose the correct technique and send the parameters to the shader (the shadow map, the view-projection matrix for each sub-frustum and also its range. I put the ranges into a single vector3 (as I have 3 cascades).
    • The shader first compute the pixel position in view space (we need that for lighting anyway), and then we use its Z to pick the correct shadow map. I’m using just a single line to do that, take a look at the shader LightingLPP.fx;
    • Convert the pixel position from camera to world space, and then to light space. Fetch the correct shadow map texel (remember that we are using a big render target with all cascades, so we need to offset it according to the selected cascade). Do the depth comparison, I’m using a 4-sampler PCF to reduce aliasing.

That is it! Here is some screenshots of this sample:

See you next time!
J.Coluna

About jcoluna

Game developer and musician
This entry was posted in XNA and tagged , , , . Bookmark the permalink.

8 Responses to XNA Light Pre-Pass: Cascade Shadow Maps

  1. fn2000 says:

    Great stuff, as always – thanks!

  2. Pingback: Geeks3D Programming Links – July 08, 2011 - 3D Tech News, Pixel Hacking, Data Visualization and 3D Programming - Geeks3D.com

  3. Pingback: Windows Client Developer Roundup 076 for 7/25/2011 - Pete Brown's 10rem.net

  4. petz7 says:

    Great, but how can i convert the directional light shadows into non-cascaded shadows?
    I’ve already tried to remove some unimportant portions in the classes and hlsl code.
    The result was: no shadows at all. The reason I’m trying to do this is to take another approach
    where i just fade out the shadows based on the distance to the near clip plane.
    Thanks again for this great sample!

  5. R T says:

    I’m getting other models to show in your example here, but the texture’s are not showing here, is there something I need to do to have them show up?
    Thanks!

  6. jcoluna says:

    Hey RT,
    You must use the hlsl shader I made available (it’s inside all the sample zip files) in the 3D max or other package you use for modelling. The model importer expects some specific texture slot names to bind to the diffuse, normal and specular maps. Look for the file “LightPrePassFX.fx”, at “LightPrePass/LightPrePassContent”.

    Hope it helps!
    -J.Coluna

  7. R T says:

    Thanks for the response! I’m using Blender 3D, I’ve looked through google, but can’t seem to find a good tutorial on using HLSL shaders and blender .fbx exporter, do you know of any good ones?
    Thanks again!

    • jcoluna says:

      Hmm sorry dude, I’m really noob at Blender. Maybe you could try to change the model processor, “LightPrePassProcesso.cs”. There are some constants to fetch the texture map from the FBX file. If you know before hand what are the slots names (you can try to open the ascii FBX and figure it out), you can change the processor to support also Blender-exported-fbx-files. Please let me know if you do it, I could include it for the next release.
      -J.Coluna

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s