Variance Shadow Maps


Shadows add a significant amount of realism to a scene by conveying information about the geometry of objects and the location and characteristics of light. A large number of techniques have been developed for rendering shadows, and even after over two decades of work there are still a lot of new developments and progress to be made. Here is a brief overview of shadows in computer graphics:

  • Ray TracingFor every pixel in the final image, a ray is shot towards each light. The point is in shadow if there is an object between the surface and the light. In more advanced ray tracing algorithms that use global illumination, shadows fall out naturally from the physics of light transport. Unfortunately, modern graphics hardware cannot yet implement this approach efficiently.
  • Classic Shadow MappingShadows are created by testing whether a pixel is visible from the light source, by comparing it to a z-buffer or depth image of the light source's view, stored in the form of a texture. This can be implemented very efficiently, but results in severe aliasing even if relatively large shadow maps are used, and the shadows are always sharp.
  • Percentage Closer FilteringA simple extension to classic shadow mapping that reduces aliasing by filtering the shadow map over a region. This requires a large number of samples of the shadow map for each screen space pixel and it can be very expensive to get good looking, antialiased soft shadows.
  • Perspective Shadow MapsOften, the light's point of view is an inefficient render location because lots of shadow map resolution is being spent on regions that are not going to be visible from the camera's location. Perspective shadow maps transform the light's camera to better make use of the shadow map resolution.
  • Cascaded Shadow MapsFor scenes with a very large range of depths, a single shadow map may be insufficient to render the scene with a reasonable amount of detail at all depth ranges. Cascaded shadow maps uses multiple shadow maps at a variety of depth ranges to deal with these scenes.
  • Variance Shadow MapsThe main expense of percentage closer filtering is that the filtering must be done in screen space. Variance shadow maps is a simple extension to classic shadow mapping that allows you to prefilter the shadow maps. An efficient, separable kernel can be used and the final render with the shadow map requires only a single texture fetch per shadow map. This lets you get large filters radii that would be much more expensive to achieve with traditional percentage closer filtering.

Overview


A normal shadow map is a 1-channel floating point texture that gives the depth to the nearest surface from the light's perspective. Variance shadow maps extend normal shadow maps by adding a second floating point channel that is the square of this depth value. These two values (which are the first two moments) can then be filtered using standard GPU texture filtering, and more importantly, Gaussian blur can be done directly on the shadow map and does not have to be done in screen-space, as in percentage closer filtering. The details are all in this paper, but it all boils down to the following 3 equations:
Equations
Here M1 and M2 are the first and second moments (which are just the two channels in the shadow map texture, after filtering.) "t" is the depth whose visibility we are interested in (the one from the user's perspective.) Computing Pmax(t) tells us what percentage of the surface should be in shadow. With sufficient Gaussian blur on the input shadow map, this gives us a nice, gradual transition between the regions that are in shadow and the regions that are fully lit, and overcomes the severe aliasing and sharp transition boundaries of shadow maps.

To simulate an area light that covers a large region of space, a large blur radius needs to be used. VSM's are excellent for this situation. To achieve a large blur radius, we can use the fact that the Gaussian blur kernel is separable. So we first blur the initial depth map along the Y-axis, then blur this new depth map along the X-axis. This is equivalent to simultaneously blurring in both axes, but needs only 2n operations instead of n2 operations. This is very significant, since for most of the results on this page, I use a 21x21 filter size.


x x x
Input Depth Map
Y-Axis Blur
X-Axis Blur

The models used below are all from the Stanford 3D Scanning Repository. Each model has over 100,000 triangles. Because my shadows do not need sharp silhouettes, the shadow maps are rendered using simplified 10,000 triangle models.

I also use a spherical light source. A single shadow map cannot capture shadows from all possible directions, so I instead use a cube map shadow map; there are actually six shadow maps, one along each direction of each Cartesian axis. The pixel shader smoothly blends the boundaries between shadow maps to avoid a sharp transition boundary.

Results


Because directly embedding an H.264 encoded video does not seem to work very uniformly across browsers, I have uploaded a results video to YouTube. Video best viewed at 1080p. For those who notice, I apologize that this is not antialiased, as I had already uploaded it before I noticed the aliasing (YouTube encoding really seems to bring those out...)


The double encoding causes a lot of ugly artifacts to show up. Here is the video in its original nearly lossless encoding, using my Video Capture program:
xShadowMap.mp4

Using the ultra-high res 100,000 triangle models, this app runs at about 86fps at 1280x720 on a GeForce 8800 GTX. If the 10,000 triangle models are used for the final render pass, it averages 141fps. Most of the expense is that it is rendering and blurring 6 shadow maps (instead of just one or two, as most realtime applications would do.)

Final shadow image using a 21x21 filter and 512x512 shadow maps:



Final shadow image using a 2x2 percentage closest filter and 512x512 shadow maps. There is massive aliasing of the shadows because of the discretization of the shadow map.



Below are results for the above scene, focusing on the bottom half of the bunny. Variance shadow maps can produce good results even with a 256x256 shadow map. Even at large shadow map sizes, there is still aliasing on the shadows with classic shadow mapping (and this will only increase for scenes with larger depth disparity.)

Shadow Map Resolution
256x256
512x512
1024x1024
Variance Shadow Maps
Classic Shadow Maps

Code


This code is all based off my BaseCode. Specifically you will need the contents of Includes.zip on your include path, Libraries.zip on your library path, and DLLs.zip on your system path.

x ShadowMap.zip (includes project file)

ShadowMap Code Listing


x App.cpp, Web Version
x App.h, Web Version
x Config.h, Web Version
x Controller.cpp, Web Version
x Controller.h, Web Version
x Engine.cpp, Web Version
x Engine.h, Web Version
x Main.cpp, Web Version
x Main.h, Web Version
x ShadowMap.cpp, Web Version
x ShadowMap.h, Web Version
x ShadowScene.cpp, Web Version
x ShadowScene.h, Web Version
xAssets
xx GaussianBlur.vs, Web Version
xx GaussianBlurX.ps, Web Version
xx GaussianBlurY.ps, Web Version
xx ShadowMapCreate.ps, Web Version
xx ShadowMapCreate.vs, Web Version
xx ShadowMapRender.ps, Web Version
xx ShadowMapRender.vs, Web Version
xx TextureColor.ps, Web Version
xx TextureColor.vs, Web Version

Total lines of code: 2024