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 Tracing — For 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 Mapping — Shadows 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 Filtering — A 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 Maps — Often, 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 Maps — For 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 Maps — The 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.
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:
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.
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.
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...)
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.)
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.ShadowMap.zip (includes project file)
ShadowMap Code Listing
App.cpp, Web Version
App.h, Web Version
Config.h, Web Version
Controller.cpp, Web Version
Controller.h, Web Version
Engine.cpp, Web Version
Engine.h, Web Version
Main.cpp, Web Version
Main.h, Web Version
ShadowMap.cpp, Web Version
ShadowMap.h, Web Version
ShadowScene.cpp, Web Version
ShadowScene.h, Web Version
GaussianBlur.vs, Web Version
GaussianBlurX.ps, Web Version
GaussianBlurY.ps, Web Version
ShadowMapCreate.ps, Web Version
ShadowMapCreate.vs, Web Version
ShadowMapRender.ps, Web Version
ShadowMapRender.vs, Web Version
TextureColor.ps, Web Version
TextureColor.vs, Web Version
Total lines of code: 2024