Using world position to sample shadows in ForwardBase pass

I’m trying to use the UNITY_LIGHT_ATTENUATION macro but it’s not working as (I) expected in the ForwardBase pass. In the ForwardAdd pass it works fine, with the third parameter affect the resulting light/shadow, while in the ForwardBase pass it seems to be ignored.

Am I doing something wrong? Is there anothger way to sample the shadows using world position?

I’m not using the URP, just the default one (SRP?). This is the stripped down shader:

Shader "Custom/Test"
{
  
    Properties
    {
        _MainTex("Base (RGB)", 2D) = "white" {}
    }
  
    CGINCLUDE
    #include "UnityStandardCore.cginc"
    ENDCG
  
    SubShader
    {

        Tags {"LightMode" = "ForwardBase"}
      
        Pass {
            Fog { Mode Off }
            Lighting On
          
            CGPROGRAM
          
            #pragma multi_compile_instancing
            #pragma multi_compile_fwdbase
            #pragma multi_compile_fog
          
            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag

            float4 _MainTex_TexelSize;

            struct appdata_t
            {
                float4 vertex            : POSITION;
                float3 normal            : NORMAL;
                float2 texcoord            : TEXCOORD0;
            };
          
            struct v2f
            {
                float4 pos                : POSITION;
                float3 texcoord         : TEXCOORD0;
                float3 posWorld            : TEXCOORD1;
                LIGHTING_COORDS(4,5)
            };
          
            v2f vert (appdata_t v)
            {
              
                v2f o;
              
                o.pos = UnityObjectToClipPos(v.vertex);
                o.texcoord = float3((TRANSFORM_TEX(v.texcoord.xy, _MainTex)).xy,1);
                o.posWorld =   mul(unity_ObjectToWorld, v.vertex);

                TRANSFER_VERTEX_TO_FRAGMENT(o);
              
                return o;
            }
          
            fixed4 frag (v2f i) : COLOR
            {
                float2 uv = i.texcoord.xy;
              
                float4 col = tex2D(_MainTex, uv);
              
                UNITY_LIGHT_ATTENUATION(atten, i, 0) // worldPos parameter ignored, shadows work fine even when set to zero, seems to be using only _ShadowCoords

                return atten;
            }
          
            ENDCG
          
        }

        Pass {
            Tags {"LightMode" = "ForwardAdd" }
            Fog { Mode Off }
            Lighting On
            ZWrite Off

            //Blend Zero One        // hide add
            Blend One Zero    // hide base

            CGPROGRAM
            #pragma target 3.0

            #pragma multi_compile_fwdadd_fullshadows
            #pragma multi_compile_fog
          
            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag
          
            float4 _MainTex_TexelSize;

            struct appdata_t
            {
                float4 vertex            : POSITION;
                float3 normal            : NORMAL;
                float2 uv                : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos                    : POSITION;
                float3 tex                    : TEXCOORD0;
                fixed3 normalWorld            : TEXCOORD1;
                LIGHTING_COORDS(2,3)
                float3 posWorld                : TEXCOORD4;
            };
          
            v2f vert (appdata_t v)
            {
                v2f o;
              
                o.pos = UnityObjectToClipPos(v.vertex);
                o.tex = float3((TRANSFORM_TEX(v.uv.xy, _MainTex)).xy,1);
              
                o.posWorld =   mul(unity_ObjectToWorld, v.vertex);
                o.normalWorld = mul( unity_ObjectToWorld, float4( v.normal, 0.0 ) ).xyz;

                TRANSFER_VERTEX_TO_FRAGMENT(o);
              
                return o;
            }
          
            fixed4 frag (v2f i) : COLOR
            {
                float2 uv = i.tex.xy;
                UNITY_LIGHT_ATTENUATION(atten, i, i.posWorld + ...) // changing posWorld paramenter affects lighting
                  return atten;
            }
          
            ENDCG
          
        }

         Pass
        {
            ZWrite On
             Tags {"LightMode"="ShadowCaster"}
          
             CGPROGRAM
             #pragma vertex vert
             #pragma fragment frag
             #pragma multi_compile_shadowcaster
             #include "UnityCG.cginc"
          
             struct v2f {
                 V2F_SHADOW_CASTER;
             };
          
             v2f vert(appdata_base v)
             {
                 v2f o;
                 TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
                 return o;
             }
          
             float4 frag(v2f i) : SV_Target
             {
                 SHADOW_CASTER_FRAGMENT(i)
             }
          
             ENDCG
         }
      
    }
}

Unity’s directional light shadows, the only kind of light supported by the base pass, don’t pass the actual shadow map(s) to the shader. Instead it renders the directional shadows to a screen space texture that uses the camera depth texture to reconstruct the world space position from from. The short of that is there’s nothing you can do to offset the world space position used to sample the shadows on a per object basis. At least not if you rely on Unity’s built in shadow systems.

There is a solution though, at least for one directional light. You can make the shadow maps be globally accessible, and then use custom shader code that samples them. There’s a project on Github that shows exactly how to do that here:

But, as I mentioned, this only works for the “main” directional light in the scene. If you have multiple shadow casting directional lights, there’s no real way to match a light to a specific pass. The screen space shadow textures for all shadow casting directional lights are calculated at the start before the main camera rendering, and there’s nothing in the add pass to say which light it is that’s running outside of crazy hackery like comparing the light direction to a list in the shader and having access to the shadow maps for all directional lights in all directional light passes.

2 Likes

I kind of went on that route already and thought “this feels too hacky, surely I’m doing something wrong” but one shadow casting directional light is all I really need so that will do.

Thank you for your help!

The ‘slightly’ less hacky feeling option would be to disable screen space shadows on desktop, but that stopped working years ago and if you try to disable screen space shadows on desktop now all directional shadows just stop working entirely. So this remains the only real option.

2 Likes

The link you posted works like a charm, I’d rather not make my life more complicated by fighting with deprecated features.

Thanks again :slight_smile: