We are building a deferred shading engine and we have a problem with shadows.

To add shadows we use two maps: the first one stores the depth of the scene captured by the player's camera and the second one stores the depth of the scene captured by the light's camera. We then ran a shader that analyzes the two maps and outputs the third one with the ready shadow areas for the current frame.

The problem we face is a classic one: Self-Shadowing: Self-Shadowing
(source: microsoft.com)

A standard way to solve this is to use the slope-scale depth bias and depth offsets, however as we are doing things in a deferred way we cannot employ this algorithm. Any attempts to set depth bias when capturing light's view depth produced no or unsatisfying results.

So here is my question: MSDN article has a convoluted explanation of the slope-scale:

bias = (m × SlopeScaleDepthBias) + DepthBias
Where m is the maximum depth slope of the triangle being rendered, defined as:
m = max( abs(delta z / delta x), abs(delta z / delta y) )

Could you explain how I can implement this algorithm manually in a shader? Maybe there are better ways to fix this problem for deferred shadows?

Update: trying to implement normal-offset shadows. Current state: enter image description here

Image with no bias applied: enter image description here

Normal offset bias applied, it works with the teapot, but look what it does with all the other shadows:

enter image description here

I even tried adding the NDotL term, it does not help:

float4 worldPosition = mul(input.Position, World);
float nDotL = dot(input.Normal,(shadowCameraPosition - worldPosition));
worldPosition.xyz += input.Normal*NormalBias*(1-nDotL);

The hardware slope-scaled depth bias should work fine with deferred shading. It only affects the depth values written into the shadow map, so you generate the same shadow map regardless of whether you're using forward or deferred shading. Is it possible you're not reconstructing position from (camera) depth correctly when you do your lighting passes? That would throw off the points where you're sampling the shadow map, which could cause issues like this.

In general, though, I prefer normal-offset shadows instead of using depth bias. When drawing the shadow map, you use the vertex shader to offset each vertex inward a small distance along its normal. This helps shadow acne without introducing the light leaking artifacts typical of slope-scaled depth bias.

| improve this answer | |
  • \$\begingroup\$ >you use the vertex shader to offset each vertex inward a small distance along its normal < How much is "a small distance"? \$\endgroup\$ – TravisG Nov 12 '13 at 19:05
  • \$\begingroup\$ @TravisG Depends on your scene - or more precisely, on the world-space size of a shadow map texel. In practice I just tweak the distance until I don't see shadow acne anymore. \$\endgroup\$ – Nathan Reed Nov 12 '13 at 19:18
  • \$\begingroup\$ Ok I am moving the vertices when drawing shadows like this: input.Position.xyz += input.Normal*NormalBias; - before doing anything else. The result is still unsatisfying. I read that I need to adjust the UV coords for the shadow map lookup, how can I do it? \$\endgroup\$ – cubrman Nov 12 '13 at 20:09
  • \$\begingroup\$ see the picture in the question. \$\endgroup\$ – cubrman Nov 12 '13 at 20:15
  • \$\begingroup\$ @cubrman Like I said, it's worth checking whether you're reconstructing position correctly and accurately. Also, I don't understand the lighting in that picture - it looks like you've got a bunch of lighting on what should be the dark side of the teapot. \$\endgroup\$ – Nathan Reed Nov 12 '13 at 20:50

I encounter the same problem when using R32Float as shadow depth, here's my code:

float dx = ddx(depth);
float dy = ddy(depth);
const float bias = max(abs(dx), abs(dy)) * YOUR_SLOPE_SCALED_BIAS;
return depth + bias;

My recommendation is to use hardware depth instead of color buffer, and the depth bias / polygon offset will work correctly; I use R32Float just to workaround a bug of some game engine.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.