Planar Shadow overlay problem

I use planar shadow as my shadow system,while there is a fetal preoblem that when two shadows overlay,it looks rather bad.
before:
6335478--703362--upload_2020-9-22_10-16-21.png

after(overlay):

6335478--703359--upload_2020-9-22_10-15-47.png
6335478--703365--16007412698073.png

shader:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "PlanarShadow/Player"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _ShadowInvLen ("ShadowInvLen", float) = 1.0 //0.4449261
        //_ShadowFalloff ("ShadowFalloff", float) = 0.1
    }
 
    SubShader
    {
        Tags{ "RenderType" = "Opaque" "Queue" = "Geometry+10" }
        LOD 100
    
        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
        
            #include "UnityCG.cginc"
        
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
        
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
        
            ENDCG
        }

        Pass
        {    
            Blend SrcAlpha  OneMinusSrcAlpha
            ZWrite Off
            Cull Back
            ColorMask RGB
        
            Stencil
            {
                Ref 0        
                Comp Equal        
                WriteMask 255    
                ReadMask 255
                //Pass IncrSat
                Pass Invert
                Fail Keep
                ZFail Keep
            }
        
            CGPROGRAM
        
            #pragma vertex vert
            #pragma fragment frag

            float4 _ShadowPlane;
            float4 _ShadowProjDir;
            float4 _WorldPos;
            float _ShadowInvLen;
            float4 _ShadowFadeParams;
            float _ShadowFalloff;
        
            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 xlv_TEXCOORD0 : TEXCOORD0;
                float3 xlv_TEXCOORD1 : TEXCOORD1;
            };

            v2f vert(appdata v)
            {
                v2f o;

                float3 lightdir = normalize(_ShadowProjDir);
                float3 worldpos = mul(unity_ObjectToWorld, v.vertex).xyz;
                float distance = (_ShadowPlane.w - dot(_ShadowPlane.xyz, worldpos)) / dot(_ShadowPlane.xyz, lightdir.xyz);
                worldpos = worldpos + distance * lightdir.xyz;
                o.vertex = mul(unity_MatrixVP, float4(worldpos, 1.0));
                o.xlv_TEXCOORD0 = _WorldPos.xyz;
                o.xlv_TEXCOORD1 = worldpos;

                return o;
            }
        
            float4 frag(v2f i) : SV_Target
            {
                float3 posToPlane_2 = (i.xlv_TEXCOORD0 - i.xlv_TEXCOORD1);
                float4 color;
                color.xyz = float3(0.0, 0.0, 0.0);
                color.w = (pow((1.0 - clamp(((sqrt(dot(posToPlane_2, posToPlane_2)) * _ShadowInvLen) - _ShadowFadeParams.x), 0.0, 1.0)), _ShadowFadeParams.y) * _ShadowFadeParams.z);
                return color;
            }
        
            ENDCG
        }
    }
}

Your options are:

  • Don’t fade out the shadow by “height”. Have them be a constant, solid shadow color. That way when they overlap it’s not a problem.
  • Clear the stencil with another pass that does the same vertex shader as the shadow, but uses ColorMask 0 or Blend Zero One so it doesn’t draw anything visible, and use Stencil { Comp Always Pass Zero } to reset the stencil buffer to zero. The result is overlapping shadows will be extra dark, but it’s less bad than too bright.
  • Render your shadow passes manually to a separate white cleared R8 render texture using BlendOp Mininstead of stencils and sample that texture in your ground material for shadows. Can be rendered at a lower resolution to reduce the cost, and / or blurred for appearance.

Thx your reply.
method1 . It will reduce the appearance
method2. another pass result more drawcall
method3 . I want to make sure that rendering shadow in one render texture for the whole scene or each building or player has one? what you mean is just like projector shadow?

Yes.

Also yes. Though thinking about it more I realized you could also solve it by changing the Stencil block and giving all “static” objects that don’t have shadows overlapping a specific Ref and each dynamic object / character their own unique ref.

// in Properties
_ShadowStencilRef ("Shadow Stencil Ref", Range(1,255)) = 1

// in Pass
Stencil {
  Ref [_ShadowStencilRef]
  Comp NotEqual
  Pass Replace
}

That will only draw the stencil where it hasn’t already drawn, just like your original Stencil block, but you can have one material use a stencil ref of 1 and another of 2 and they’ll draw on top of each other rather than clearing the previous one.

Still won’t look correct, but won’t look as bad as what you have.

All projected shadows for all objects need to be rendered into that one render texture prior to rendering the scene. And then yes, it’s similar to having a projector, but way, way cheaper. Overdraw is the enemy of mobile, and this would be having a custom ground shader that samples from the render texture using screen space UVs and multiplying the output color. Really very cheap compared to a projector. Potentially cheaper than what you’re currently doing if you use a lower resolution render texture.

The problem is you want the shadows that’s seen to be the darkest part of the shadow. The only way to do that is to be able to use a min comparison between the output of the shader and what’s already been rendered. However you can’t do this in-scene, since the output color of the shadow has little to do with the ground color.

What you have right now is something like this:
6337707--703827--upload_2020-9-22_10-1-0.png

Option 2 would give you something like this:
6337707--703830--upload_2020-9-22_10-1-30.png

Option 3 would give you:
6337707--703839--upload_2020-9-22_10-5-24.png

But that can only be achieved by rendering out a texture like this:
6337707--703842--upload_2020-9-22_10-6-36.png

As the shadows are outputting black and doing a min function (BlendOp Min) so only the darkest pixel is retained. Note, no stencil is needed!

If you do that same blend mode, but against the ground itself you’d just get this instead:
6337707--703845--upload_2020-9-22_10-8-15.png
Notice how the dark areas are a solid color and any area that’s already darker than the shadow in the faded areas aren’t affected at all? That’s as “good as you can get” using blend modes / stencils alone.

method 3:
all objects render to one render texture every frame during runtime? There are many buildings and players. and players can move and play animations. btw how about fps for method3?

I have found a plugin named
Fast Shadow Projector

It project shadow to one rendertexture, while when the render texture is static ,so when player play animation the shadow looks bad