PDA

View Full Version : Why does my variance shadow map need an offset?

jbizzler
25-Mar-2007, 20:49

Andrew Lauritzen
26-Mar-2007, 02:45
You don't need an offset (bias), although numeric precision issues will still cause a slight problem (which is unavoidable without exact rational arithmetic :)). The easiest solution is to clamp the variance to a small value before calculating Chebyshev's inequality, something like:

Variance = E_x2 - E_x*E_x;
Variance = max(Variance, 0.000001);
p_max = ...

The value that you clamp to is not really related to the scene, and a very small value will usually remove all self-shadowing artifacts without affecting the "real" shadows at all. The value also does not need tweaking: it really can be set once and forgotten about. You can also do some fancier math related to the surface normal (or depth derivatives), but that's probably overkill since clamping the variance works so well in my experience.

Regarding depth metrics: if you're using a directional light, you can use the light-space Z value (BEFORE diving by z!!!) as the depth metric, and that will work well. The easiest way of implementing this is something like:

LightSpacePos = mul(Position, LightViewMatrix);
Output.Position = mul(LightSpacePos, LightProjectionMatrix);
Output.Depth = (LightSpacePos.z - NearClipPlane) / (FarClipPlane - NearClipPlane);

Alternatively (if you're using a "normal" orthogonal projection matrix) you can just divide Output.Position by FarClipPlane to save a few instructions, but the above is a bit more clear on what's going on.

For a spot or point light, the best depth metric in my experience is "distance to light", which must actually be calculated by interpolating the light space position (float3) and computing the length in the pixel shader (the NVIDIA demo actually does it incorrectly I believe).

jbizzler
26-Mar-2007, 03:16
Thank you so much. I had to clamp it to 0.00000001. Any higher and the shadow stops showing up completely. Now all I have to do blur it and it'll be as beuatiful as your demo.

jbizzler
26-Mar-2007, 03:28
One more thing. I notice your demo uses 4 32bit components. The NVIDIA demo only uses 2, the depth and its square. What are those other values?

Andrew Lauritzen
26-Mar-2007, 03:30
The amount that you clamp to will be dependent on your depth metric... the value that I gave works pretty well if you're rescaling to the [0, 1] range. Use whatever is appropriate for your application of course :)

My demo used 4 components to split precision, since summed-area tables eat precision for breakfast! If you're only doing normal variance shadow maps + blur (which is definitely preferable if you don't need per-pixel variable filter widths for soft shadows or something), 2x fp32 is quite enough!

jbizzler
26-Mar-2007, 03:58
Okay, sweet. Thanks so much. I've been looking for a good shadow solution for a long time now, and this is by far the best yet. I'm gonna code a blur tomorrow morning. If I have any questions, I'll let you know here.

Andrew Lauritzen
26-Mar-2007, 14:43
No problem. Good luck with your implementation!

jbizzler
26-Mar-2007, 15:17
Okay, I think I just about have the blur working. I just have a small problem. I have the blur vertex shader generate the position and texture coordinate based on the vertex id, like the NVIDIA demo does, so I don't even need a vertex buffer or an input layout. This is how the NVIDIA demo does it, but wehn I do it, I recieve this error:

D3D10: ERROR: ID3D10Device::Draw: The Vertex Shader expects application provided input data (which is to say data other than hardware auto-generated values such as VertexID or InstanceID). Therefore an Input Assembler object is expected, but none is bound. [ EXECUTION ERROR #349: DEVICE_DRAW_INPUTLAYOUT_NOT_SET ]

So why do I need an input layout and they don't?

Andrew Lauritzen
26-Mar-2007, 15:36
So why do I need an input layout and they don't?
You shouldn't... I do the same thing and don't use a layout, etc. Make sure that you're not taking any other inputs to the vertex shader, and that you've bound the input assembler streams to NULL.

jbizzler
26-Mar-2007, 16:21
I'm so dumb. I never applied the technique. Awesome, it works now. Thank you so much for all yoru help.