Sunday, October 21, 2012

Cascaded Variance Shadow Maps

Previously on Devblog.Drheinous.com...

Heh.  Anyway, previously I'd talked about cascaded shadow maps and how they worked.  As I said implementing them was easy as Neil Knight had done most of the hard work.  The quality still wasn't quite good enough for the voxel terrain my engine could push. I could easily draw a lot of terrain and have the far clip plane way out, but as it got bigger the quality of the shadows suffered.  CSM mostly fixed it, but still left some aliasing mostly due to self-shadowing.  Here's a good example from the GPU Gems chapter on the subject:

Another problem with percentage-closer filtering is that it inherits, and indeed exacerbates, the classic nuisances of shadow mapping: "shadow acne" and biasing. The most difficult to solve biasing issues are caused by shadow-map texels that cover large ranges of depths. This scenario often results from polygons that are almost parallel to the light direction, as shown in Figure 8-3.

08fig03.jpg
This would be Figure 8-3, GPU Gems 3 illustration of shadow mapping artifacts.



Enter Variance Shadow Maps.  There is a bunch of math in the Donnelly and Lauritzen paper and in the GPU Gems article, but I'll boil it down simply.

Instead of writing the depth of a pixel to the depth map (which is then used to generate the end shadow map) you write the depth and the depth squared.  Well, actually depth and depth squared plus a bias based on the partial derivatives (from the effects file for drawing models):

// depth squared is for variance shadow maps.
    float moment2 = fDepth * fDepth;
    float dx = ddx(fDepth);
    float dy = ddy(fDepth);
    moment2 += 0.25 * (dx * dx + dy * dy) ;

    return float4(fDepth, moment2,0,0);

And as the GPU Gems 3 article illustrates, this gets rid of all the nasty and intractable biasing issues (the odd dark stripes in the shot above):

This value is independent of the scene geometry, so it can be set once without requiring incessant tweaking. Thus, biasing alone is a compelling reason to use variance shadow maps, as demonstrated in Figure 8-5.
08fig05.jpg
GPU Gems 3 Figure 8-5.
 I won't get to detailed into the algorithm, it's all in the paper or GPU Gems 3 article, certainly better than I can explain.

I did make several errors initially, though.  I implemented the update to the depth map shaders to write the depth and depth squared, then added the calculation of the Chebyshev Upper Bound when calculating the actual shadow map.  For all that work, it didn't really buy me anything, there were still artifacts, if slightly different ones.

I probably spent two or three days fiddling with various example algorithms (mostly alternating between the NVIDIA algorithm and the Microsoft one.  They were functionally mostly identical, but I was SURE I had something wrong here and so kept floundering and switching to different methods).

I eventually found a nice clean example implementing VSM and finally realized you take the depth map and run a blur filter against it, THEN use it to build the shadow map.  Basically doing a Gaussian blur to a new render target and using that as the depth map worked wonders.  The wonderful example project is here at Electronic Meteor... after looking into it was when I realized what I was missing was the blur step (another site with lots of shadowing technique discussion linked from there is here, well worth the read).

In effect what Iwas missing amounts to substituting the blur for the X-by-X PCF lookup and averaging.  This gets you the soft edged shadows, and you can also take advantage of mipmapping and texture filtering, improving the result further, as well as significantly speeding up the entire process.  I haven't done any benchmarks, but I can tell that the Variance shadow maps are a lot lighter than the PCF ones by rather blatant margins.

There are still a few problems; the borders between the cascades are visible and also the Variance shadows are showing some light-bleeding, a known problem with the algorithm.  But I can clean all that up, I believe.  For now I'm fairly happy, it is working well and fairly performant:

Current state of the shadows.

2 comments:

  1. Thanks for giving my blog a mention. That variance shadow mapping sample is pretty old now :P I played around with VSM for a while, but it was too finicky and didn't get them to work with CSM. I went with Poisson disc sampling instead, which I find just as good and easier to implement IMO.

    By the way, the GUI editor looks simple and promising. Are you using MFC/Winforms or a more graphical UI?

    ReplyDelete
  2. I have the shadows working pretty well. Mostly through general tinkering, though. The only problem is that there seems to be a slight shadow 'halo' around objects. Probably due to blurring, but it's very minor and I just haven't chased it down.

    I'm really astonished how easy the Awesiomium GUI has proven to be. After having messed with various XNA / Ogre / DirectX frameworks this has proven to be... well, easy. It's trivial to open the UI HTML file, add a new HTML button, then link the javascript to a C# callback. That's all it takes.

    Anyway, I've had a few other people express interest in exactly how it works, so I'm working up a more detailed post on it.

    (And at some point I need to move to the new version of Awesomium. 1.7 supposedly is much improved, but there are breaking changes).

    ReplyDelete