Edit: I demonstrated some harder cases for the important filters here. I also used a brighter light so that it’s easier to see the shadow edges
Over the past two weeks I’ve been working on various parts of my game engine’s shadow mapping, mostly experimenting with different shadow filtering techniques.
For reference, above are two images of a simple test scene without any shadow map filtering applied.
I started with basic 4 tap PCF, using a simple averaging of the samples. See this filtering below.
Then I moved on to edge-tap smoothing of the 4 tap PCF filtering method:
Then I experimented with using more than 4 samples:
Below is an implementation of the dithering idea in the article Shadow Map Antialiasing from GPU Gems 1.
The GPU Gems 1 method above selects shadow sampling positions based on the fragment’s current screenspace coordinates. So I tried rotating the 4 samples based on the current fragment position (in screen space) instead. See the image below.
I also tried doing the rotations based on object space instead:
Then I tried using a small lookup texture (32×32) with random 2×2 matrix rotations, used to offset the 4 samples in random directions based on a screenspace sampling of the random noise texture. See below.
It’s worth noting that even though the last two methods shown are the same and both use 4 samples, the first method chooses samples from a larger area than the second. This makes the penumbra larger, hides the underlying shadow map better, but also costs more texture bandwidth due to worse cache coherency. The 202 sampling area PCF requires a large bias, to the point where even with slope-scale biasing, I couldn’t choose a small enough bias to avoid shadow peter-panning completely.
The last method shown actually produces pretty good looking shadows, and is quite efficient. It also allows for an easily modifiable sampling area, which could be very useful — more on this later.
None of these filtering methods produced particularly smooth looking static shadows. Except for the last method displayed, all of these shadows “shimmer” horribly when the camera or light source moves. The rotated disc method shadows also shimmer, but the effect is not nearly as bad as the other PCF sampling methods. And even though they are all pretty fast, they require fairly large shadow maps. All of the images so far have used 2048×2048 shadow maps.
Next I started implementing exponential shadow maps (ESMs). These allow us to blur the shadow map or otherwise filter it before drawing the light, and then we can take a single sample from the shadow map, using an approximation of the shadow penumbra. This method avoids biasing problems that large penumbra PCF sampling has. It also requires less texture bandwidth.
I started by using 2xMSAA when creating the shadow map (which effectively blurs the shadow map edges). A 2048×2048 shadow map is used, and 2xMSAA doubles the size of the texture at creation time. This makes this texture 32MB in total, which is far larger than the Xbox 360′s 10MB eDRAM, and therefore far too large to be practical on the Xbox 360. A 5×5 seperable gaussian blur is also applied to the shadow map before drawing the light.
I also tried taking 4 samples of the shadow map, creating the shadow map with 4xMSAA, and not even bothering with a prefiltering step (so there is no gaussian blurring of the shadow map). This is potentially a faster method, and is the method that I am currently using for point light shadows (which are less dominant lights than the directional light shown in all of the screenshots so far).
Since the results of the ESM with a gaussian blur prefilter are quite good, I started reducing the shadow map size. Below is a 512×512 shadow map with 4xMSAA (which means that the texture easily fits into the XBox EDRAM and therefore 4xMSAA incurs very little texture bandwidth cost). A 5×5 separable gaussian blur is applied so that a single sample is enough to produce a smooth penumbra.
This technique is fast and looks good. One problem with it though, is that the penumbra size is fixed, which is not realistic. In real life, light is scattered around, and light sources are not ideal points or directional lights. Percentage closer soft shadow (PCSS) approximate real life shadow penumbras by determining where the penumbra should be sharper or softer. Shadow penumbras are typically sharper close to the occluding object, and softer further from the object — instead of soft everywhere like in the image above.
The problem with a pre-filtered shadow map using one ESM tap, is that you cannot change the size of the penumbra by expanding the sampling kernel size (as there is only 1 sample) and the shadow map prefiltering used a fixed size kernel (a 5×5 Gaussian in this case). This is something that is easier with PCF, but as we saw, PCF does not look good with only a few samples. PCSS can be expensive, and is only an approximation — and not a very good one when there are multiple layered occluders. So this is a problem that I’ll defer until later. Possible solutions include varying the ESM exponent to vary the penumbra size, or combining PCF sampling techniques shown earlier with ESM to vary the kernel size.