Shadow shader shades all shadows based on only 1 objects position

Hello,

I’ve been working on a project where we want to have shadows. I made 2 objects: a cube and a raptor. These 2 objects should emit the shadow on a plane i put under these objects. The plane has the shadow shader. I want the intensity of the objects shadow to be based on the distance to the light. The issue is: if i move the raptor further away from the light, the cube also decreases in intensity.

(Don’t know why the image doesnt work. link: noMove hosted at ImgBB — ImgBB)
The directional light is in the middle and gives a shadow to both the objects.
Now when i move the directional light, i would want the raptor to have a more intense shadow and the cube a less intense shadow. As you can see in the image below the intensity of the cube is the exact same as the raptor.

(link to image: move hosted at ImgBB — ImgBB)

How do i make seperate intensities per object?
This is the shader:

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

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/MatteShadow"
{
    Properties
    {
        _ShadowStrength("Shadow Strength", Range(0, 1)) = 1
        _ShadowRange("Shadow Range", Range(1,20)) = 10
    }
        SubShader
    {

        Tags
    {
        "Queue" = "AlphaTest+49"
        "IgnoreProjector" = "True"
        "RenderType" = "Transparent"
    }
        Pass
    {
        Tags{ "LightMode" = "ForwardBase" }
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase

    float4 shadowObjects[20];
    float4 lightPosition;

    struct AppDatata {
        float4 pos : POSITION;
    };

#include "UnityCG.cginc"
#include "AutoLight.cginc"
    struct v2f
    {
        float4 pos : SV_POSITION;
        float3 worldPosition : TEXCOORD2;
        //float intensity : TEXCOORD3;
        //float normalizedDistance : TEXCOORD1;
        SHADOW_COORDS(0)
    };

    fixed _ShadowStrength;
    fixed _ShadowRange;
    v2f vert(appdata_img v)
    {
        v2f o;
        o.pos = UnityObjectToClipPos(v.vertex);
        o.worldPosition = mul(unity_ObjectToWorld, v.vertex).xyz;

        TRANSFER_SHADOW(o);
        return o;
    }
    fixed4 frag(v2f v) : COLOR
    {
        fixed shadow = SHADOW_ATTENUATION(v);
        fixed shadowalpha = (1.0 - shadow) * _ShadowStrength;

        if (shadowalpha == 0) {
            return fixed4(0.0, 0.0, 0.0, 0.0);
        }

        float intensity = 0;
        for (int i = 0; i < 2; i++)
        {
            if (any(shadowObjects[i].xyz != fixed3(0.0, 0.0, 0.0))) { // nullcheck to prevent division by zero
                float normalizedIntensity = (length(lightPosition.xyz - shadowObjects[i].xyz)) / _ShadowRange; // _ShadowRange
                float saturation = 1 - saturate(normalizedIntensity);
                intensity += saturation;
            }
        }

        return fixed4(0.0, 0.0, 0.0, shadowalpha * intensity); // using a light

    }
        ENDCG
    }
        Pass
    {
        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
    }
    }
}

Also i pass the lights position and the objects position to the shader every update:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PassShaderValues : MonoBehaviour {

    [SerializeField]
    private Material _material;

    [SerializeField]
    private GameObject[] _shadowObjects;
    [SerializeField]
    private GameObject _light;

    private Vector4[] _shadowPositions = new Vector4[5]; // just temp size

    void Start () {
    }
   
    // Update is called once per frame
    void Update () {
        for (int i = 0; i < _shadowObjects.Length; i++) {
            Vector3 pos = _shadowObjects[i].transform.position;
            _shadowPositions[i] = new Vector4(pos.x, pos.y, pos.z, 1.0f);
        }
        _material.SetVectorArray("shadowObjects", _shadowPositions);
        _material.SetVector("lightPosition", _light.transform.position);
    }
}

What am I doing wrong?

This one line is the problem. The way Unity’s shadows work objects do not individually “emit” shadows as atomic things. Rather each light generates a shadow map that includes all shadow casting objects within their view. A shadow map is effectively a texture rendered from the point of view of the light that records the distance to the closest surface for each pixel. When rendering the shadows into the camera’s view you calculate each pixel’s position relative to the light. If that position is the same as the closest position in the shadow map, it is in light, otherwise it is in shadow.

Any knowledge of the separate objects that cast the shadow, or any way to modify the opacity of the shadow in relation to those original objects, is lost.

TLDR; What you want is not possible to do while using Unity’s built in lighting system.

To do what you want you would need to render the shadows yourself. Your best bet may not even necessarily involve “real” shadows or shadow maps since you only seem to care about the opacity of shadows from specific objects cast onto a plane.

There are a few ways to go about this that I can think of. One is to use a shader that projects the objects onto a mathematical plane and draws them as a solid color. This is actually really simple, especially if your plane is always flat on the y axis.

float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
worldPos.xyz -= (_WorldSpaceLightPos0.xyz / _WorldSpaceLightPos0.y) * (worldPos.y - _WorldSpaceShadowPlaneHeight);
o.pos = mul(UNITY_MATRIX_VP, worldPos);

However it should be noted just doing this will result in either z fighting and/or overlapping geometry being darker so you’ll need to either use a stencil or BlendOp to combat that. If the shadows from two objects will never overlap then the stencil will work with out a problem.

3250637--250245--stencil shadows.png
Fake Shadow Shader

Shader "FakeShadow"
{
    Properties
    {
        _Color("Shadow Color", Color) = (0,0,0,0.5)
        _WorldSpaceShadowPlaneHeight("Shadow Plane Height", Float) = 0
        _Stencil("Shadow Stencil Value (Int)", Range(1,255)) = 1
    }
    SubShader
    {
        // queue "AlphaTest+50" = 2500, the first transparent queue
        Tags { "Queue"="AlphaTest+50" "RenderType"="Transparent" }
        LOD 100

        Pass
        {
            Tags { "LightMode"="ForwardBase" }

            Stencil {
                Ref [_Stencil]
                Comp NotEqual
                Pass Replace
                Fail Keep
            }

            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
          
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
            };

            fixed4 _Color;
            float _WorldSpaceShadowPlaneHeight;
          
            v2f vert (appdata v)
            {
                v2f o;
                float3 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz;
                worldPos -= _WorldSpaceLightPos0.xyz / _WorldSpaceLightPos0.y * (worldPos.y - _WorldSpaceShadowPlaneHeight);
                o.pos = mul(UNITY_MATRIX_VP, float4(worldPos.xyz, 1));
                return o;
            }
          
            fixed4 frag (v2f i) : SV_Target
            {
                return _Color;
            }
            ENDCG
        }
    }
}

These are two meshes with two materials, first the default material and second the fake shadow material. Also while the further away object in this example has the darker shadow, obviously it doesn’t have to be. They’re just like that for this example.

Two issues with this technique. One is that overlaps of the same stencil value will choose which ever draws first. As you can see in the image above the sphere, which is closer, draws first thus it’s lighter shadow takes precedence over the “bush” mesh shadow that’s floating above it, and slightly further away. The other issue is that when the object casting the shadow is out of view the shadow will be culled!

The easiest work around for the second problem I can think of for this would be to have separate game objects that use the shadow material and use a script to place them on the ground where the shadow should be to ensure they don’t get culled. For the first problem you’d need to control the draw order at runtime to ensure the “darkest” shadow gets drawn first. You could use https://docs.unity3d.com/ScriptReference/Renderer-sortingOrder.html for that via scripting, or the material queue (but that can get ugly if you have a lot of objects).

Another option would be to setup a camera to render the objects from the point of view of the light, but using a replacement shader pass or direct Graphics.DrawMesh call to render them. Then project that texture onto your shadow plane.

A yet third option would be to render custom per-object shadow maps and inject them into the scene at custom opacities … but that’s getting way more complicated quickly for not a ton of benefit.

Thanks a lot for your reply!
I don’t really need an overlap in shadows, because I am using the shadows as an indication for where an object is in world space (i’m using AR). I am still gonna implement the overlap, but with the first option you gave. As I said I am only going to use the shadows for position indication so I won’t need a lot of objects.

I am gonna try something, thanks a lot for your help!