I did not know why I didn’t implement this sooner. I know before I said that I was happy enough with the shadows that I will not focus on any more major improvements, but looks like I lied 😛 So presenting, Poisson disc shadow filtering. Basically, I ditched the 4-sample manual linear filter code to go back to the basics and added Poisson filter. There’s already quite a few blog articles that talk about out, not the least which is useful for shadow mapping. This shadow mapping tutorial (but with GLSL code) covers it in an easy to understand way.
For that reason, I won’t go into the details, but generally speaking, you take a number of samples from a given area that are spaced as evenly as possible. Then you blend the opacity of each sampled shadow to get the result. It eliminates the “pixely” look well while avoiding too much random distribution. Here’s the handy tool I used to generate a bunch of sample locations, which I plugged into the shader code. I am fine just using this for static values, and not bother with coding my own generation algorithm for this.
Here’s a few screenshots with Poisson filtered cascaded shadow mapping and was surprised at the results. The simplicity of using a standard shadow map and jittering in a bunch of directions makes shadow edges appear nice and soft at a glance.
Here is one with the lighting and shadows only, before adding gamma correction:
And one showing point lights interacting with the shadow color. I find this one good enough to add to the front page of my Codeplex site.
All these screenshots use a 16-tap filter. I could’ve used more, but it does require a texture lookup for each sample and I hit the shader instruction limit beyond 20 samples. The blocky artifacts are still present when seen up close, but they are definitely better than the artifacts you get from PCF shadow mapping. The way in which the samples clump together give a good illusion of out-of-focus areas that give shadows their soft appearance.
There are also other ways to apply the filter, most commonly rotated Poission disc for each texel, or randomize the sample lookup. I didn’t use these for the following reasons- I didn’t see much of a benefit to rotate the disc each time, as the pattern is already hard to discern. With random lookups, the point is to produce noisy gradient edges instead of banded edges. This is expensive to do when you add up all the samples, and I actually think the noisy edges look like crap. The noise pattern moves when the camera moves, which is very distracting. It’s almost as bad as the shimmering effect, and I prefer still looking shadows.
So here, I think, is the easiest approach to approximating soft shadows, while still looking very good. No more attempting to use variance shadow maps or exponential shadow maps… they have their own pitfalls. All you need is a simple shadow map and a bunch of (well-placed) samples to get going.