Search Unity

Shadow shader shades all shadows based on only 1 objects position

Discussion in 'Shaders' started by thomasbaake33, Oct 11, 2017.

  1. thomasbaake33

    thomasbaake33

    Joined:
    Feb 23, 2016
    Posts:
    2
    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: https://ibb.co/dk4fLb)
    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: https://ibb.co/cSHT6G)

    How do i make seperate intensities per object?
    This is the shader:
    Code (CSharp):
    1. // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
    2.  
    3. // Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
    4.  
    5. Shader "Custom/MatteShadow"
    6. {
    7.     Properties
    8.     {
    9.         _ShadowStrength("Shadow Strength", Range(0, 1)) = 1
    10.         _ShadowRange("Shadow Range", Range(1,20)) = 10
    11.     }
    12.         SubShader
    13.     {
    14.  
    15.         Tags
    16.     {
    17.         "Queue" = "AlphaTest+49"
    18.         "IgnoreProjector" = "True"
    19.         "RenderType" = "Transparent"
    20.     }
    21.         Pass
    22.     {
    23.         Tags{ "LightMode" = "ForwardBase" }
    24.         ZWrite Off
    25.         Blend SrcAlpha OneMinusSrcAlpha
    26.         CGPROGRAM
    27. #pragma vertex vert
    28. #pragma fragment frag
    29. #pragma multi_compile_fwdbase
    30.  
    31.     float4 shadowObjects[20];
    32.     float4 lightPosition;
    33.  
    34.     struct AppDatata {
    35.         float4 pos : POSITION;
    36.     };
    37.  
    38. #include "UnityCG.cginc"
    39. #include "AutoLight.cginc"
    40.     struct v2f
    41.     {
    42.         float4 pos : SV_POSITION;
    43.         float3 worldPosition : TEXCOORD2;
    44.         //float intensity : TEXCOORD3;
    45.         //float normalizedDistance : TEXCOORD1;
    46.         SHADOW_COORDS(0)
    47.     };
    48.  
    49.     fixed _ShadowStrength;
    50.     fixed _ShadowRange;
    51.     v2f vert(appdata_img v)
    52.     {
    53.         v2f o;
    54.         o.pos = UnityObjectToClipPos(v.vertex);
    55.         o.worldPosition = mul(unity_ObjectToWorld, v.vertex).xyz;
    56.  
    57.         TRANSFER_SHADOW(o);
    58.         return o;
    59.     }
    60.     fixed4 frag(v2f v) : COLOR
    61.     {
    62.         fixed shadow = SHADOW_ATTENUATION(v);
    63.         fixed shadowalpha = (1.0 - shadow) * _ShadowStrength;
    64.  
    65.         if (shadowalpha == 0) {
    66.             return fixed4(0.0, 0.0, 0.0, 0.0);
    67.         }
    68.  
    69.         float intensity = 0;
    70.         for (int i = 0; i < 2; i++)
    71.         {
    72.             if (any(shadowObjects[i].xyz != fixed3(0.0, 0.0, 0.0))) { // nullcheck to prevent division by zero
    73.                 float normalizedIntensity = (length(lightPosition.xyz - shadowObjects[i].xyz)) / _ShadowRange; // _ShadowRange
    74.                 float saturation = 1 - saturate(normalizedIntensity);
    75.                 intensity += saturation;
    76.             }
    77.         }
    78.  
    79.         return fixed4(0.0, 0.0, 0.0, shadowalpha * intensity); // using a light
    80.  
    81.     }
    82.         ENDCG
    83.     }
    84.         Pass
    85.     {
    86.         Tags{ "LightMode" = "ShadowCaster" }
    87.         CGPROGRAM
    88. #pragma vertex vert
    89. #pragma fragment frag
    90. #pragma multi_compile_shadowcaster
    91. #include "UnityCG.cginc"
    92.     struct v2f {
    93.         V2F_SHADOW_CASTER;
    94.     };
    95.     v2f vert(appdata_base v)
    96.     {
    97.         v2f o;
    98.         TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
    99.             return o;
    100.     }
    101.     float4 frag(v2f i) : SV_Target
    102.     {
    103.         SHADOW_CASTER_FRAGMENT(i)
    104.     }
    105.         ENDCG
    106.     }
    107.     }
    108. }
    Also i pass the lights position and the objects position to the shader every update:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class PassShaderValues : MonoBehaviour {
    6.  
    7.     [SerializeField]
    8.     private Material _material;
    9.  
    10.     [SerializeField]
    11.     private GameObject[] _shadowObjects;
    12.     [SerializeField]
    13.     private GameObject _light;
    14.  
    15.     private Vector4[] _shadowPositions = new Vector4[5]; // just temp size
    16.  
    17.     void Start () {
    18.     }
    19.    
    20.     // Update is called once per frame
    21.     void Update () {
    22.         for (int i = 0; i < _shadowObjects.Length; i++) {
    23.             Vector3 pos = _shadowObjects[i].transform.position;
    24.             _shadowPositions[i] = new Vector4(pos.x, pos.y, pos.z, 1.0f);
    25.         }
    26.         _material.SetVectorArray("shadowObjects", _shadowPositions);
    27.         _material.SetVector("lightPosition", _light.transform.position);
    28.     }
    29. }
    30.  
    What am I doing wrong?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    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.

    stencil shadows.png
    Code (CSharp):
    1. Shader "FakeShadow"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Shadow Color", Color) = (0,0,0,0.5)
    6.         _WorldSpaceShadowPlaneHeight("Shadow Plane Height", Float) = 0
    7.         _Stencil("Shadow Stencil Value (Int)", Range(1,255)) = 1
    8.     }
    9.     SubShader
    10.     {
    11.         // queue "AlphaTest+50" = 2500, the first transparent queue
    12.         Tags { "Queue"="AlphaTest+50" "RenderType"="Transparent" }
    13.         LOD 100
    14.  
    15.         Pass
    16.         {
    17.             Tags { "LightMode"="ForwardBase" }
    18.  
    19.             Stencil {
    20.                 Ref [_Stencil]
    21.                 Comp NotEqual
    22.                 Pass Replace
    23.                 Fail Keep
    24.             }
    25.  
    26.             Blend SrcAlpha OneMinusSrcAlpha
    27.             ZWrite Off
    28.  
    29.             CGPROGRAM
    30.             #pragma vertex vert
    31.             #pragma fragment frag
    32.          
    33.             #include "UnityCG.cginc"
    34.  
    35.             struct appdata
    36.             {
    37.                 float4 vertex : POSITION;
    38.             };
    39.  
    40.             struct v2f
    41.             {
    42.                 float4 pos : SV_POSITION;
    43.             };
    44.  
    45.             fixed4 _Color;
    46.             float _WorldSpaceShadowPlaneHeight;
    47.          
    48.             v2f vert (appdata v)
    49.             {
    50.                 v2f o;
    51.                 float3 worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1)).xyz;
    52.                 worldPos -= _WorldSpaceLightPos0.xyz / _WorldSpaceLightPos0.y * (worldPos.y - _WorldSpaceShadowPlaneHeight);
    53.                 o.pos = mul(UNITY_MATRIX_VP, float4(worldPos.xyz, 1));
    54.                 return o;
    55.             }
    56.          
    57.             fixed4 frag (v2f i) : SV_Target
    58.             {
    59.                 return _Color;
    60.             }
    61.             ENDCG
    62.         }
    63.     }
    64. }

    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.
     
  3. thomasbaake33

    thomasbaake33

    Joined:
    Feb 23, 2016
    Posts:
    2
    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!