Advertisement

Is there a quick way to fix peter panning (shadows detaching from objects)

Started by February 02, 2018 11:06 AM
7 comments, last by turanszkij 7 years ago

My shadows (drawn using a depth buffer which I later sample from in the shadow texture), seem to be detaching slightly from their objects.

 

I looked this up and I think it's "peter panning," and they were saying you have to change the depth offset, but not sure how to do that.

https://msdn.microsoft.com/en-us/library/windows/desktop/ee416324(v=vs.85).aspx

 

Is there a fast way I can tweak the code to fix this? Should I change the "bias" perhaps, or something else? Thanks,

Here is the code for the shadows, for reference:


// ENTRY POINT
float4 main(PixelInputType input) : SV_TARGET
{
    float2 projectTexCoord;
    float depthValue;
    float lightDepthValue;
    //float4 lightColor = float4(0,0,0,0);
    float4 lightColor = float4(0.05,0.05,0.05,1);

    // Set the bias value for fixing the floating point precision issues.
    float bias = 0.001f;

    //////////////// SHADOWING LOOP ////////////////
    for(int i = 0; i < NUM_LIGHTS; ++i)
    {
    // Calculate the projected texture coordinates.
    projectTexCoord.x =  input.vertex_ProjLightSpace[i].x / input.vertex_ProjLightSpace[i].w / 2.0f + 0.5f;
    projectTexCoord.y = -input.vertex_ProjLightSpace[i].y / input.vertex_ProjLightSpace[i].w / 2.0f + 0.5f;

    if((saturate(projectTexCoord.x) == projectTexCoord.x) && (saturate(projectTexCoord.y) == projectTexCoord.y))
    {
        // Sample the shadow map depth value from the depth texture using the sampler at the projected texture coordinate location.
        depthValue = depthTextures[i].Sample(SampleTypeClamp, projectTexCoord).r;

        // Calculate the depth of the light.
        lightDepthValue = input.vertex_ProjLightSpace[i].z / input.vertex_ProjLightSpace[i].w;

        // Subtract the bias from the lightDepthValue.
        lightDepthValue = lightDepthValue - bias;

        // Compare the depth of the shadow map value and the depth of the light to determine whether to shadow or to light this pixel.
        // If the light is in front of the object then light the pixel, if not then shadow this pixel since an object (occluder) is casting a shadow on it.
            if(lightDepthValue < depthValue)
            {
                // Calculate the amount of light on this pixel.
                float lightIntensity = saturate(dot(input.normal, normalize(input.lightPos_LS[i])));

                if(lightIntensity > 0.0f)
                {
                    float spotlightIntensity = CalculateSpotLightIntensity(input.lightPos_LS[i], cb_lights[i].lightDirection, input.normal);
                    //lightColor += (float4(1.0f, 1.0f, 1.0f, 1.0f) * lightIntensity) * .3f; // spotlight
                    lightColor += float4(1.0f, 1.0f, 1.0f, 1.0f) /** lightIntensity*/ * spotlightIntensity * .3f; // spotlight
                }
            }
        }
    }

    return saturate(lightColor);
}

https://github.com/mister51213/DirectX11Engine/blob/master/DirectX11Engine/MultiShadows_ps.hlsl

 

peterpanning.PNG

The purpose of the depth offset (called bias in your code) is to prevent objects causing themselves to be in shadow due (which you can see if you set it to zero)

So try reducing it slightly maybe half it to .0005f

That should move the shadows slightly closer to the object, but if you reduce it too far it may cause self shadows in which case you would have to increase it slightly maybe to .0008f

Play around with its value to make it look right

 

Advertisement

You can also try SlopeScaledDepthBias in the Rasterizer State settings to have better shadows when the light not faces geometry straight but sloped.

You can also draw your objects in the shadow map with reversed culling order, so culling front faces instead of backfaces. Or disabling culling could also help.

The short answer is no. The longer answer is also no. Nothing is ever simple with shadows in real time rendering.

 

The problem is that a shadow-map texel will cover a variable amount of screen pixels. The well seen result without any counter measure is aliasing as the surface on screen goes up and down the unique shadow map texel. The problem get worse with shadow map bluring like multi tap PCF because you end fetching further, increasing odds of false positive and negatives.

 

Depth bias of any sort, as at shadow map render or as shadow test read are a useful tool but are never perfect. The idea is to push the whole scissor like stairs depth of the shadow map texels under the screen surface. By definition, they generate your peter panning effect. You can have lighting artist spending their days tweaking the biasing for every light and still not have a perfect result. The slope based bias is making your bias bigger where the acne is the most likely ( perpendicular surface that will extend over way more on screen projection ), and by so, will add even more floating :)

 

If you look at recent attempt to solve that, there is a "crazy" technique doing real raytrace up close that then blend to shadow map. The demo came from nVidia and have been implemented at least once (afaik) in Battlefield 1 for their Sun.

8 hours ago, turanszkij said:

You can also try SlopeScaledDepthBias in the Rasterizer State settings to have better shadows when the light not faces geometry straight but sloped.

You can also draw your objects in the shadow map with reversed culling order, so culling front faces instead of backfaces. Or disabling culling could also help.

Thanks great tips Turanzkij. Quick questions

1) I see SlopeScaledDepthBias is currently set 0 in my engine - what value do you suggest I set?

2) Will changing it cause problems w the rendering of the other objects in the scene?

3) Can I change the SlopeScaledDepthBias dynamically (set it to one value before I draw the shadows and back to 0 after I draw them)?

3) For reversed culling, I should turn off culling right before I draw the shadows to the depth map, and then turn culling on again afterwards, is that correct?

9 hours ago, mister345 said:

Thanks great tips Turanzkij. Quick questions

1) I see SlopeScaledDepthBias is currently set 0 in my engine - what value do you suggest I set?

2) Will changing it cause problems w the rendering of the other objects in the scene?

3) Can I change the SlopeScaledDepthBias dynamically (set it to one value before I draw the shadows and back to 0 after I draw them)?

3) For reversed culling, I should turn off culling right before I draw the shadows to the depth map, and then turn culling on again afterwards, is that correct?

  1. Sorry I am not sure. Best to leave it unless you find a value that actually helps you. You can read about how it affects the bias calculation: MSDN
  2. Only change it for the rasterizer state which you use for shadow rendering. You should use separate rasterizer states for shadow rendering and normal object rendering.
  3. You can do that if you follow my suggestion to use separate rasterizer states for shadow and normal object rendering.
  4. Only turn on reversed culling for the shadow map rendering rasterizer state. Though this can eliminate peter-panning, it can introduce other problems.

Good luck!

 

Advertisement
38 minutes ago, turanszkij said:
  1. Sorry I am not sure. Best to leave it unless you find a value that actually helps you. You can read about how it affects the bias calculation: MSDN
  2. Only change it for the rasterizer state which you use for shadow rendering. You should use separate rasterizer states for shadow rendering and normal object rendering.
  3. You can do that if you follow my suggestion to use separate rasterizer states for shadow and normal object rendering.
  4. Only turn on reversed culling for the shadow map rendering rasterizer state. Though this can eliminate peter-panning, it can introduce other problems.

Good luck!

 

When I switch to front face culling, it introduced new gaps in teh shadows, so I had to completely turn off culling - seemed to improve, but with a 10 frame drop!

For bias, I found these values online, they seem to be based on Frank Luna -


RasterizerState Depth
{
    DepthBias = 10000;
    DepthBiasClamp = 0.0f;
    SlopeScaledDepthBias = 1.0f;
};

I set the bias to the above numbers (they used to all be 0 by default), honestly cant tell if it makes any difference.

@mister345 An other thing to consider is the precision of your shadow maps. 32bit floating point shadow maps will get you much better precision than a 16bit integer one. And it also affects the bias calculations, so you must use different bias values for them.

In my engine I am currently using depthbiasclamp = 0, depthbias = 0, slopescaleddepthbias = -2.0f values for the shadow rasterizers. Additionally I can set a bias per light which will be calculated in the pixel shader before comparing the shadow map and reference depths by simply adding the bias value to the reference depth (spotlight, directional light). For point lights I calculate the bias like this: 


// dist is distance between light and pixel world pos
float referenceDist = 1 - dist / light.range * (1 - light.shadowBias);

Note that my point lights are storing distances in the shadow map, not depth like spotlight and directional light.

An in my engine, all shadow maps are 16 bits integer (DXGI_FORMAT_D16_UNORM).

This topic is closed to new replies.

Advertisement