Peter Panning Shadows In Unity

I’d like to find the cause of some problems I’ve seen with shadow casting lights in Unity and in order to do so I’d like to be able to edit and generally experiment with changes to the standard deferred shaders. It turns out you can do just that!

For reference this is the problem I want to address:

First step is to download built in shaders from here

Extract Internal-DeferredShading.shader and rename it. I’ve called it GOAT-DeferredShading.shader… just because I like goats… but you can call it anything. From there import the new shader into the project under Resources, and under the scenes Graphics Settings change Deferred to Custom Shader and point it at the imported shader.

Now we can try a modification to double check it’s all hooked up. Any modification to CalculateLight should be immediately visible in the game view. Try faking the GBuffer inputs or something similar and you should see the result right away if the new shader is working.

I’m trying to find the cause of some shadow caster Peter Panning problems so in order to ensure the relevant code is available I also import UnityDeferredLibrary and UnityShadowLibrary, renaming them and then replacing the includes to chain the files together such that the custom versions of the code are used instead of the original versions.

Almost right away I spot this code in UnityShadowLibrary.cginc:

inline half UnitySampleShadowmap (float3 vec)
    float mydist = length(vec) * _LightPositionRange.w;
    mydist *= 0.97; // bias

Hard coded numbers always make me suspicious so I try eliminating it, and this is the result.

This image shows shadow acne. The cause is where the depth of the occluder is more or less the same as the depth of the receiver, and the precision of the depth buffer isn’t enough to resolve the depth difference, so we end up with pixels incorrectly marked as in shadow. In effect the surface here is casting a shadow onto itself. The standard way of dealing with this would be to default to casting shadows from back faces only… where from the point of view of a light the back faces are never visible and are therefore always in shadow and are almost always deep enough to not interfere with the front facing polygons shadow calculations. Is this not how shadows work in Unity?

Changing this hard coded bias does however appear to fix the peter panning problems… it just causes lots of other problems at the same time.

If I’m going to fix this properly I need to be able to control the shaders that write to the shadow map. It turns out you can do that too. Unity comes with a shader called Standard.shader that is used by the standard materials. As before we can extract this, rename to GOAT-Standard.shader and import it. Then we apply this small modification to switch from front face to back face shadow casters…

     // ------------------------------------------------------------------
     //  Shadow rendering pass
     Pass {
        Name "ShadowCaster"
        Tags { "LightMode" = "ShadowCaster" }
        ZWrite On ZTest LEqual		
        Cull Front  // <---------------------------

At the same time we change that we can comment out the line that multiplies the distance by 0.97… and as if by magic everything works more or less the way I wanted it to. With the bias removed a small amount of flickering is visible where the pillar joins the floor, but with this setup a tiny negative bias is actually more appropriate, so I end up with code that looks like this instead

inline half UnitySampleShadowmap (float3 vec)
    float mydist = length(vec) * _LightPositionRange.w;
    mydist *= 1.01; // bias

And now the shadows are totally solid, and match the geometry perfectly!

Leave a Reply

Your email address will not be published. Required fields are marked *