Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Shadow receiver: what am I doing wrong?

Discussion in 'Shaders' started by Sinterklaas, Apr 28, 2021.

  1. Sinterklaas

    Sinterklaas

    Joined:
    Jun 6, 2018
    Posts:
    93
    Hi,

    I've been working on a shader for lit sprites, and it's coming along quite nicely. However, I cannot for the life of me figure out how to make my sprites receive shadows properly.

    Below is a heavily stripped-down version of the shader, which renders a solid red color and receives shadows. However, as you can see in the screenshot, shadows aren't received properly. Instead, there is this "see-through" effect, where shadows behind the object are visible, but no shadows are cast on the object.

    I know it's not a problem with the sprite renderer, as I used the mesh renderer in the image below. Using a sprite renderer (with shadow receiving enabled through code) produces the same effect.

    I've also tried setting the queue tag to "Geometry", as I suspected it might be a depth testing problem, but this doesn't seem to change anything.

    Does anyone have an idea what I'm missing here? Cause I have this gut feeling that the solution is probably pretty simple, but I just can't figure it out.

    Code (CSharp):
    1. Shader "Custom/ShadowCollector"
    2. {
    3.     SubShader
    4.     {
    5.         Tags {"Queue"="AlphaTest" }
    6.         Pass
    7.         {
    8.             Tags {"LightMode" = "ForwardBase" }
    9.             Cull Back
    10.             Blend SrcAlpha OneMinusSrcAlpha
    11.          
    12.             CGPROGRAM
    13.             #pragma vertex vert
    14.             #pragma fragment frag
    15.             #pragma multi_compile_fwdbase
    16.             #include "UnityCG.cginc"
    17.             #include "AutoLight.cginc"
    18.          
    19.             struct v2f
    20.             {
    21.                 float4 pos : SV_POSITION;
    22.                 float4 worldPos : TEXCOORD0;
    23.                 SHADOW_COORDS(1)
    24.             };
    25.          
    26.             v2f vert(appdata_base v)
    27.             {
    28.                 v2f o;
    29.                 o.pos = UnityObjectToClipPos (v.vertex);
    30.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    31.                 TRANSFER_SHADOW(o)
    32.                 return o;
    33.             }
    34.          
    35.             fixed4 frag(v2f i) : SV_TARGET
    36.             {
    37.                 UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);
    38.                 float shadow = attenuation;
    39.                 fixed4 color = fixed4(1, 0, 0, 1);
    40.                 color.rgb *= shadow;
    41.                 return color;
    42.             }
    43.             ENDCG
    44.         }
    45.     }
    46. }
     
  2. Sinterklaas

    Sinterklaas

    Joined:
    Jun 6, 2018
    Posts:
    93
    Ok, so I've added a shadow casting pass to my shader, and suddenly everything works now?? Which is great, but I'm still not entirely sure why? Can only materials with a shadow casting pass receive shadows or something?

    I'm even more confused than before now, so if anyone can explain what exactly is going on here, that'd be much appreciated!
     
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,208
    Yes.

    Unity's directional shadows are cast onto the camera depth texture, not the actual geometry. The depth texture is generated by rendering the scene from the camera's view with all objects using their shadow caster pass. Unity then renders the shadows onto the world position extrapolated from the depth texture into a full screen texture, and opaque objects in the scene sample from that full screen texture to get their shadow when rendering normally.

    Check out the Frame Debugger window under Windows > Analysis > Frame Debugger.
     
  4. Sinterklaas

    Sinterklaas

    Joined:
    Jun 6, 2018
    Posts:
    93
    Well that totally clears things up. I was under the wrong assumption that directional light shadows were rendered according to a depth texture from the light's pov, instead of a screen-space shadow texture from the camera's pov. That explains why my non-casters showed the shadows that were behind them - they didn't write to the shadow texture, so their screen positions lined up with the shadows of objects behind them.

    I also see now that it's possible to actually view the shadow texture in the frame debugger, that really helps visualize things.

    Well, consider that a lesson learned. Thanks a ton!
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,208
    You're not wrong though. Shadows are generally rendered using a depth texture rendered from the "view" (or views plural for a point light) of a light called a shadow map. That is still true here. Directional lights often use cascaded shadow maps, meaning multiple shadowmaps covering the view of the camera at different scales & ranges. When doing shadows, the shader then samples the shadow map and checks if the position it as sampling from is further away from the light than the value in the shadow map for that position in the light's "view".

    The camera depth texture is similar to a shadow map in that both are depth textures. However in this case Unity is sampling the camera depth texture to extrapolate the world space position to check against the shadow map.

    The reason it renders the shadows this way is a little complicated, but the short version is it's a bit faster than rendering it all at once in one pass, especially on older hardware.