Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Planar Shadow overlay problem

Discussion in 'Shaders' started by Deleted User, Sep 22, 2020.

  1. Deleted User

    Deleted User

    Guest

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


    after(overlay):

    upload_2020-9-22_10-15-47.png
    16007412698073.png


    shader:
    Code (CSharp):
    1. // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
    2.  
    3. Shader "PlanarShadow/Player"
    4. {
    5.     Properties
    6.     {
    7.         _MainTex ("Texture", 2D) = "white" {}
    8.         _ShadowInvLen ("ShadowInvLen", float) = 1.0 //0.4449261
    9.         //_ShadowFalloff ("ShadowFalloff", float) = 0.1
    10.     }
    11.  
    12.     SubShader
    13.     {
    14.         Tags{ "RenderType" = "Opaque" "Queue" = "Geometry+10" }
    15.         LOD 100
    16.    
    17.         Pass
    18.         {
    19.             CGPROGRAM
    20.  
    21.             #pragma vertex vert
    22.             #pragma fragment frag
    23.             // make fog work
    24.             #pragma multi_compile_fog
    25.        
    26.             #include "UnityCG.cginc"
    27.        
    28.             struct appdata
    29.             {
    30.                 float4 vertex : POSITION;
    31.                 float2 uv : TEXCOORD0;
    32.             };
    33.  
    34.             struct v2f
    35.             {
    36.                 float2 uv : TEXCOORD0;
    37.                 UNITY_FOG_COORDS(1)
    38.                 float4 vertex : SV_POSITION;
    39.             };
    40.  
    41.             sampler2D _MainTex;
    42.             float4 _MainTex_ST;
    43.  
    44.             v2f vert (appdata v)
    45.             {
    46.                 v2f o;
    47.                 o.vertex = UnityObjectToClipPos(v.vertex);
    48.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    49.                 UNITY_TRANSFER_FOG(o,o.vertex);
    50.                 return o;
    51.             }
    52.        
    53.             fixed4 frag (v2f i) : SV_Target
    54.             {
    55.                 // sample the texture
    56.                 fixed4 col = tex2D(_MainTex, i.uv);
    57.                 // apply fog
    58.                 UNITY_APPLY_FOG(i.fogCoord, col);
    59.                 return col;
    60.             }
    61.        
    62.             ENDCG
    63.         }
    64.  
    65.         Pass
    66.         {    
    67.             Blend SrcAlpha  OneMinusSrcAlpha
    68.             ZWrite Off
    69.             Cull Back
    70.             ColorMask RGB
    71.        
    72.             Stencil
    73.             {
    74.                 Ref 0        
    75.                 Comp Equal        
    76.                 WriteMask 255    
    77.                 ReadMask 255
    78.                 //Pass IncrSat
    79.                 Pass Invert
    80.                 Fail Keep
    81.                 ZFail Keep
    82.             }
    83.        
    84.             CGPROGRAM
    85.        
    86.             #pragma vertex vert
    87.             #pragma fragment frag
    88.  
    89.             float4 _ShadowPlane;
    90.             float4 _ShadowProjDir;
    91.             float4 _WorldPos;
    92.             float _ShadowInvLen;
    93.             float4 _ShadowFadeParams;
    94.             float _ShadowFalloff;
    95.        
    96.             struct appdata
    97.             {
    98.                 float4 vertex : POSITION;
    99.             };
    100.  
    101.             struct v2f
    102.             {
    103.                 float4 vertex : SV_POSITION;
    104.                 float3 xlv_TEXCOORD0 : TEXCOORD0;
    105.                 float3 xlv_TEXCOORD1 : TEXCOORD1;
    106.             };
    107.  
    108.             v2f vert(appdata v)
    109.             {
    110.                 v2f o;
    111.  
    112.                 float3 lightdir = normalize(_ShadowProjDir);
    113.                 float3 worldpos = mul(unity_ObjectToWorld, v.vertex).xyz;
    114.                 float distance = (_ShadowPlane.w - dot(_ShadowPlane.xyz, worldpos)) / dot(_ShadowPlane.xyz, lightdir.xyz);
    115.                 worldpos = worldpos + distance * lightdir.xyz;
    116.                 o.vertex = mul(unity_MatrixVP, float4(worldpos, 1.0));
    117.                 o.xlv_TEXCOORD0 = _WorldPos.xyz;
    118.                 o.xlv_TEXCOORD1 = worldpos;
    119.  
    120.                 return o;
    121.             }
    122.        
    123.             float4 frag(v2f i) : SV_Target
    124.             {
    125.                 float3 posToPlane_2 = (i.xlv_TEXCOORD0 - i.xlv_TEXCOORD1);
    126.                 float4 color;
    127.                 color.xyz = float3(0.0, 0.0, 0.0);
    128.                 color.w = (pow((1.0 - clamp(((sqrt(dot(posToPlane_2, posToPlane_2)) * _ShadowInvLen) - _ShadowFadeParams.x), 0.0, 1.0)), _ShadowFadeParams.y) * _ShadowFadeParams.z);
    129.                 return color;
    130.             }
    131.        
    132.             ENDCG
    133.         }
    134.     }
    135. }
    136.  
     
    Last edited by a moderator: Sep 22, 2020
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    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 Min
      instead 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.
     
  3. Deleted User

    Deleted User

    Guest

    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?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    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.
    Code (csharp):
    1. // in Properties
    2. _ShadowStencilRef ("Shadow Stencil Ref", Range(1,255)) = 1
    3.  
    4. // in Pass
    5. Stencil {
    6.   Ref [_ShadowStencilRef]
    7.   Comp NotEqual
    8.   Pass Replace
    9. }
    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:
    upload_2020-9-22_10-1-0.png

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

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

    But that can only be achieved by rendering out a texture like this:
    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:
    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.
     
    Last edited: Sep 22, 2020
  5. Deleted User

    Deleted User

    Guest

    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?
     
  6. Deleted User

    Deleted User

    Guest

    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