Sign in to follow this  
jbizzler

Bluring Dynamic Variance Shadow Maps

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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;
};

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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 :)

// Global
const int SplitPowLookup[8] = {0, 1, 1, 2, 2, 2, 2, 3};

// In fragment shader

// Compute which split we're in
int 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.

Share this post


Link to post
Share on other sites
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 ...

Share this post


Link to post
Share on other sites
That was 1024^2, just to get nice quality. However 512^2 and 3 slices looks pretty darn good with even just 4x MSAA on the shadow map (G80 supports MSAA for fp32 render targets!)... even 2 slices of 512^2 looks pretty nice IMHO, although a bit less detailed naturally. Probably 512^2 with 3 slices is the best trade-off right now on the G80, although it will of course depend on the scene.

I'll be posting the demo binary publicly soon so anyone with a G80 and Vista (D3D10) is welcome to play around with it: pretty much every option is exposed in the UI.

Share this post


Link to post
Share on other sites
I am looking forward to the demo! My upper limit is 672x672 (platform specific: this way I can get 4xMSAA) and I can double like this: 15meter, 30 meter, 60 meter and 120 meter. Usually the last one can take more. My meters are relative so they do not say a lot.
With 4xMSAA my 672 maps are like 1024 maps ... pretty cool.

Share this post


Link to post
Share on other sites
Just for the record, 672 is a weird size ;)

Quote:
Original post by wolf
With 4xMSAA my 672 maps are like 1024 maps ... pretty cool.

Yeah multisampling with VSMs is awesome - it makes static shots look better, but it *really* makes a huge difference in motion, completely eliminating swimming edges in many cases.

Anyways I'll be posting the demo soon... just have to clean up a few things :)

Share this post


Link to post
Share on other sites
Mine's still not really working. Here's my SampleState and my horozontal blur. It works fine on the first slice, but the 2nd slice is pixellated like a traditional shadow map, and beyond that, everything is in shadow.

SamplerState Linear
{
AddressU = Clamp;
AddressV = Clamp;
Filter = MIN_MAG_MIP_LINEAR;
};

float4 PS_BlurH(PS_Blur_In input) : SV_TARGET
{
int softness = 3;
float4 output = (float4)0;
float step = 8.0f / (512.0f * BlurWidth[LightID]);

float2 tex;
for(int i = -softness; i <= softness; i++)
{
tex = input.Tex + float2(i * step, 0);
output += ShadowMap[0].Sample(Linear, tex);
}

return output / (2 * softness + 1);
}

I tried SampleLevel, and it produced the same results. The softness is actually like a 7x7 box blur, just clearer code to me, and the BlurWidth is the width of the corrosponding orthographic projection matrix.

And what are the code tags on this forum?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this