Bluring Dynamic Variance Shadow Maps

Started by
12 comments, last by jbizzler 17 years ago
Since I'm cascading variance shadow maps, the amount I blur I do doesn't produce consistent results when moving around. For example, I look at something one way, and the blur is stretched a little more vertically, and another, and it's stretched horozontally. Even worse, when a shadow is between the two shadow maps, it's extremely obvious because the further one is blurier. I've tried all sorts of ways to vary the blur based on the VSMs ortho projection's width and height, but they generally fail, producing inconsistent results. Any suggestions?
Advertisement
You have to blur (in each dimension) based on the ratio of texel sizes between the slices. If you're constructing a "zoom matrix" that you post-multiply on to the projection matrix, it will contain the scale factors that you need in the _11 and _22 positions. Use these per-slice scale factors to modify the blur filter width for each slice.
Zoom matrix?
How are you constructing the projection matrix for each slice? One obvious and easy way is to "zoom in" on a portion of the standard shadow projection matrix.
I find the 8 points of the frustum, transform them to light-space, find the difference between the max and min x and y, make an ortho projection based on those difference, then translate the whole thing to the middle of the box made by the maxes and mins. I've passed those widths and heights made by the ortho projections to the blur effect, but no matter what I do with them, there's always some sampling error that creates crazy lines. These problems seem to increase the further the slices get.

Edit: I think it's how I sample. This is the samplerstate I use:

SamplerState Linear
{
AddressU = Clamp;
AddressV = Clamp;
Filter = MIN_MAG_MIP_LINEAR;
};
The scale factors that you're looking for are proportional to your "max - min" for the slice.

Regarding the sampling, that's a great setting for VSM. There's one detail though... at split points the derivatives are going to wonky, which will cause a bad LOD to be selected (or worse). This can cause some nasty artifacts - potentially the ones that you're seeing.

The simples solution is to not use mipmapping, although that isn't ideal for VSMs. Still, it may work for you, so just try using SampleLevel(..., ..., 0) instead of Sample.

You can also compute your own derivatives and pass them to SampleGrad, although that's a little bit of differential geometry math.

Lastly, you can use a hack that I developed for specifically this purpose :) Basically the idea is to ensure that every pixel in quad chooses the same slice so that the derivatives will work out. I'm not going to post the code quite yet as it needs a bit more testing, but it seems to work like a charm so far. Check out the preliminary images:
Parallel-Split Variance Shadow Maps
Visualized Splits

I'll probably post a demo soon @ Beyond3D. Code and chapter will be part of GPU Gems 3, although I probably won't specifically address parallel-split shadow maps in the chapter text.
Hey Andy,
you got me again with this shot :-). Looks cool.
I am curious about two things: the way you construct each light view frustum ... it is probably orthographic. And how your trick works ... hey you mentioned it twice in the forum but you did not show it.
My orthographic light frustum is done like this:

1. determine a slice of the view frustum
2. calculate a near and far plane for this
3. transform in light space
4. take the extrema and build a bounding box
5. calculate width and height for the orthographic frustum for this
6. construct the orthographic frustum

When I think about it, the light space transform seems to be a more critical part than I thought. Currently I just construct a light matrix with a x and y direction that is always the same, for this ... but I think I will change this tomorrow. I want the orthographic frustum to be more or less nailed to the eight points on the viewer frustum and it should not rotate around it.

- Wolfgang
When I just go by my "max - min", the first and second slices work, but passed that, everything appears in shadow. Maybe clamp it?

SampleLevel produced identical results, and I have no idea how to do SampleGrad. Your sample looks beautiful, though. I'm very curious about that.
Quote:Original post by wolf
Hey Andy,
you got me again with this shot :-). Looks cool.

I'm pretty happy with the results so far, although they are preliminary.

Quote:Original post by wolf
I am curious about two things: the way you construct each light view frustum ... it is probably orthographic.

Actually it's still a perspective projection (since it's a spot light, this is desirable), just an off-centered one. It could be computed directly using (for example) D3DXMatrixPerspectiveOffCenter, but since I already have a projection matrix for the light, it's easier just to modify that projection to "zoom in" on the region bounded in normalized device coordinates.

I compute my slices similar to the demo here. I believe that's pretty similar to what you're doing. The idea is to project the bounding volume of the frustum split into light space and then render a slice that includes objects in that region, *and any that may cast shadows into that region*. Since we're rendering from the light's point of view, the castors can be taken care of easily by not modifying the near place of each slice (since any castors will be nearer than the current slice).

Of course for a directional light, this all works the same with, except with an orthographic projection instead of perspective.

Quote:Original post by wolf
And how your trick works ... hey you mentioned it twice in the forum but you did not show it.

Alright, I'll post the code segment, but with a few heavy warnings:
1) I just made this up yesterday so while it seems to work flawlessly, I haven't tested it extensively.
2) It is extremely hackish and relies on the way that GPUs calculate derivatives. However the entire problem that it solves is due to the way that GPUs compute derivatives, so it's not really that bad.
3) What it does is *force all pixels in a hardware 2x2 quad to choose the same split*. This in turn makes sure that texture derivatives are well-defined everywhere.
4) This code is for a maximum of four slices, however it can easily be extended to handle more if required.
4) While it is quite hacky, I'm still pretty proud of it :)
// Globalconst int SplitPowLookup[8] = {0, 1, 1, 2, 2, 2, 2, 3};// In fragment shader// Compute which split we're inint Split = dot(1, Input.SliceDepth > g_Splits);    // Ensure that every fragment in the quad choses the same split so that// derivatives will be meaningful for proper texture filtering and LOD// selection.int SplitPow = 1 << Split;int SplitX = abs(ddx(SplitPow));int SplitY = abs(ddy(SplitPow));int SplitXY = abs(ddx(SplitY));int SplitMax = max(SplitXY, max(SplitX, SplitY));Split = SplitMax > 0 ? SplitPowLookup[SplitMax-1] : Split;

Note that the SplitPowLookup array is just an efficient way of computing log2(x) where x is between 1 and 8.

Quote:Original post by wolf
When I think about it, the light space transform seems to be a more critical part than I thought.

It's an extremely critical step actually since if it is done badly, entire slices can be wasted. My implementation isn't bullet-proof, nor particularly good even... in a real game it'd probably be best to use some sort of "geometry approximation" as suggested in the Parallel-Split Shadow Maps paper (Google it).

Regarding problems with PSSM+VSM, there are really only a few extra details:
- if blurring, make sure to enlarge your split region in NDC by the blur size to ensure that penumbraes aren't cut off between splits
- the blur size of each split should be scaled by the texel size ratio of that slice to the whole "normal" shadow map.
- derivatives are problematic across splits... use the above hack or compute them manually
- make sure that you check if z < 0 before dividing by w when projecting frustum corners! If this is the case, you need to either be conservative and make the split cover the whole shadow map, or else write a proper homogenious coordinates clipping algorithm...

These are really all things that are true of any PSSM implementation, it's just that they don't show up as problems until you try to do proper texture filtering, which most (if not all) implementations that I've seen don't bother to do.

Regarding your specific problems jbizzler, it's probably the easiest for me just to point you to the code once I release it. I think I have pretty much exactly the implementation that you're trying to do, and looking at the code will probably be a lot more enlightening than my bad explanations...

PS: There was a bug in the split code when I took those pictures, so don't study them too closely ;) It is fixed now and splits in the distance look even better now.
Thanks a lot for the info. I have one other question: what shadow map resolution are you using in the shot?
I am currently experimenting with resolutions much under 800 ...

This topic is closed to new replies.

Advertisement