Custom shadow mapping - source code

Very simple shadow mapping post processing effect.
In Unity Editor:

  • Create New Scene;
  • Add ShadowMapping component to Main Camera
  • Assign “ShadowMapping” shader
  • Add “3D Object/Sphere” to the hierarchy window and assign as Light in ShadowMapping component
  • Add some geometry to the scene (for example terrain component)
  • Play (shadows will be only visible in Game View)
  • Light object now has added light camera component. Change light camera properties to change field of view etc. In scene view, see light camera frustum.
  • Play with Shadow Bias parameter.
using UnityEngine;

public class ShadowMapping : MonoBehaviour
{
    public Shader ShadowMappingShader;
    public GameObject Light;
    [Range(0.0f, 5.0f)] public float ShadowBias = 1.0f;
    public bool InvertY = true;

    private Camera _LightCamera;
    private Camera _MainCamera;   
    private Material _Material;
    private RenderTexture _RenderTexture;

    void Start()
    {
        _LightCamera = Light.AddComponent<Camera>();
        _LightCamera.renderingPath = RenderingPath.DeferredShading;
        _MainCamera = this.gameObject.GetComponent<Camera>();
        _MainCamera.depthTextureMode = _LightCamera.depthTextureMode = DepthTextureMode.Depth;
        _Material = new Material(ShadowMappingShader);
        _RenderTexture = new RenderTexture(4096, 4096, 32, RenderTextureFormat.Depth);
        _RenderTexture.Create();
        _LightCamera.targetTexture = _RenderTexture;
    }

    void OnRenderImage (RenderTexture source, RenderTexture destination)
    {
        _LightCamera.Render();
        Matrix4x4 lightViewProjection = GL.GetGPUProjectionMatrix(_LightCamera.projectionMatrix, true) * _LightCamera.worldToCameraMatrix;
        _Material.SetMatrix("_LightViewProjection", lightViewProjection);
        _Material.SetFloat("_ShadowBias", ShadowBias);
        _Material.SetFloat("_InvertY", System.Convert.ToSingle(InvertY));
        Matrix4x4 m = GL.GetGPUProjectionMatrix(_MainCamera.projectionMatrix, false);
        m[2, 3] = m[3, 2] = 0.0f; m[3, 3] = 1.0f;
        Matrix4x4 projectionToWorld = Matrix4x4.Inverse(m * _MainCamera.worldToCameraMatrix) * Matrix4x4.TRS(new Vector3(0, 0, -m[2,2]), Quaternion.identity, Vector3.one);
        _Material.SetMatrix("_ProjectionToWorld", projectionToWorld);
        Graphics.Blit (source, destination, _Material);
    }   

    void OnDestroy()
    {
        _RenderTexture.Release();
        Destroy(_Material);
    }
}
Shader "ShadowMapping"
{
    Properties
    {
        [HideInInspector] _MainTex ("Texture", 2D) = "black" {}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex VSMain
            #pragma fragment PSMain
            #pragma target 5.0

            float4x4 _ProjectionToWorld, _LightViewProjection;
            sampler2D _CameraDepthTexture, _LastCameraDepthTexture, _MainTex;
            float _ShadowBias, _InvertY;

            float4 VSMain (in float4 vertex : POSITION, inout float2 uv : TEXCOORD0, out float3 direction : TEXCOORD1) : SV_POSITION
            {
                float4 position = UnityObjectToClipPos(vertex);
                direction = mul(_ProjectionToWorld, float4(position.xy, 0.0, 1.0)) - _WorldSpaceCameraPos;
                return position;
            }

            void PSMain (float4 vertex : SV_POSITION, float2 uv : TEXCOORD0, float3 direction : TEXCOORD1, out float4 fragColor : SV_TARGET)
            {
                float sceneDepth = 1.0 / (_ZBufferParams.z * tex2D(_CameraDepthTexture, uv.xy) + _ZBufferParams.w);
                float3 worldSpace = direction * sceneDepth + _WorldSpaceCameraPos;
                float4 shadowCoord = mul(_LightViewProjection, float4(worldSpace, 1.0));
                float2 projCoords = (shadowCoord.xy / shadowCoord.w) * 0.5 + 0.5;
                float depth = (shadowCoord.z / shadowCoord.w);
                if (_InvertY > 0.5f) projCoords.y = 1.0 - projCoords.y;
                float closestDepth = 1.0 / (_ZBufferParams.z * tex2D(_LastCameraDepthTexture, projCoords.xy).r + _ZBufferParams.w);
                float currentDepth = 1.0 / (_ZBufferParams.z * depth + _ZBufferParams.w);
                float shadow = ((currentDepth - _ShadowBias) < closestDepth) || (depth > 0.5) ? 0.0 : 1.0;
                float4 color = float4(1.0 - shadow.xxx, 1.0);
                bool isLightFrustum = depth > 0.0;
                isLightFrustum = isLightFrustum && projCoords.x >= 0.0 && projCoords.x <= 1.0;
                isLightFrustum = isLightFrustum && projCoords.y >= 0.0 && projCoords.y <= 1.0;
                isLightFrustum = isLightFrustum && sceneDepth <= (_ProjectionParams.z - _ProjectionParams.y);
                float4 baseColor = tex2D(_MainTex, uv);
                fragColor = isLightFrustum ? ((color.r > 0.5) ? baseColor * float4(1,1,1,1) : baseColor * float4(0,0,0,1)) : baseColor;
            }
            ENDCG
        }
    }
}

1 Like

Hi, thanks for this. I need some basic implementation to iterate upon that will render shadows on very far away mountains, like 500km away but could start where Unity shadows fade. I would need to modify the code for this, but maybe yours would be a good start?

Unfortunately, your scripts give me the following error in Unity URP 2023.2:
9624194--1366631--upload_2024-2-5_5-28-11.png

Do you know what’s the problem and how to fix it? Have to get it working in URP.

Example was written for Built-In mode, not for URP/HDRP.

" very far away mountains, like 500km " it is very, very, very far away.