How to make unlit shader that casts shadow?

I know that unlit shadows by default cannot cast shadows.
However, I need a shader that does not receive ambient light but is able to cast shadows. Does anyone know how to make such a shader?
I am absolutely clueless when it comes to coding shaders sadly but I really do need this shader for my project.

Thank you all.

You need to either set a fallback shader that has a shadow caster pass or wirte your own shadow caster pass. Here is a simple unlit shader (with some fancy stencil support):

Shader "Supyrb/Unlit/Texture"
{
    Properties
    {
        [HDR]_Color("Albedo", Color) = (1,1,1,1)
        _MainTex ("Texture", 2D) = "white" {}
       
        [Header(Stencil)]
        _Stencil ("Stencil ID [0;255]", Float) = 0
        _ReadMask ("ReadMask [0;255]", Int) = 255
        _WriteMask ("WriteMask [0;255]", Int) = 255
        [Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp ("Stencil Comparison", Int) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _StencilOp ("Stencil Operation", Int) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _StencilFail ("Stencil Fail", Int) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _StencilZFail ("Stencil ZFail", Int) = 0
       
        [Header(Rendering)]
        _Offset("Offset", float) = 0
        [Enum(UnityEngine.Rendering.CullMode)] _Culling ("Cull Mode", Int) = 2
        [Enum(Off,0,On,1)] _ZWrite("ZWrite", Int) = 1
        [Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Int) = 4
        [Enum(None,0,Alpha,1,Red,8,Green,4,Blue,2,RGB,14,RGBA,15)] _ColorMask("Color Mask", Int) = 15
    }
   
    CGINCLUDE
    #include "UnityCG.cginc"

    half4 _Color;
    sampler2D _MainTex;
    float4 _MainTex_ST;
   
    struct appdata
    {
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
    };

    struct v2f
    {
        float2 uv : TEXCOORD0;
        float4 vertex : SV_POSITION;
    };

    v2f vert (appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        return o;
    }
   
    half4 frag (v2f i) : SV_Target
    {
        return tex2D(_MainTex, i.uv) * _Color;
    }
    struct v2fShadow {
        V2F_SHADOW_CASTER;
        UNITY_VERTEX_OUTPUT_STEREO
    };

    v2fShadow vertShadow( appdata_base v )
    {
        v2fShadow o;
        UNITY_SETUP_INSTANCE_ID(v);
        UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
        TRANSFER_SHADOW_CASTER_NORMALOFFSET(o)
        return o;
    }

    float4 fragShadow( v2fShadow i ) : SV_Target
    {
        SHADOW_CASTER_FRAGMENT(i)
    }
   
    ENDCG
       
    SubShader
    {
        Stencil
        {
            Ref [_Stencil]
            ReadMask [_ReadMask]
            WriteMask [_WriteMask]
            Comp [_StencilComp]
            Pass [_StencilOp]
            Fail [_StencilFail]
            ZFail [_StencilZFail]
        }
       
        Pass
        {
            Tags { "RenderType"="Opaque" "Queue" = "Geometry" }
            LOD 100
            Cull [_Culling]
            Offset [_Offset], [_Offset]
            ZWrite [_ZWrite]
            ZTest [_ZTest]
            ColorMask [_ColorMask]
           
            CGPROGRAM
            #pragma target 3.0
            #pragma vertex vert
            #pragma fragment frag
            ENDCG
        }
       
        // Pass to render object as a shadow caster
        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode" = "ShadowCaster" }
            LOD 80
            Cull [_Culling]
            Offset [_Offset], [_Offset]
            ZWrite [_ZWrite]
            ZTest [_ZTest]
           
            CGPROGRAM
            #pragma vertex vertShadow
            #pragma fragment fragShadow
            #pragma target 2.0
            #pragma multi_compile_shadowcaster
            ENDCG
        }
    }
}

The important part is the last pass :slight_smile:

7 Likes

Thanks so much man. Really appreciate you taking your time to help me.
Gonna try this out soon.:smile:

Thank you for this! Works great. But… what about receiving shadows… is that more difficult? Basically I have a cartoon like character witch is “unlit” in style, but still would be good if it received and casts shadows. Any ideas?

In the built-in shaders you can get something similar under Nature/Tree Soft Occlusion Leaves…if you dont feel like importing custom stuff.

3 Likes

Hey,
I’m currently looking for the same thing, wondering if you find a solution?

Unlit textured with shadow receiving and shadow casting:

Shader "UnlitWithShadows"
{
    Properties
    {
        _TextureMap ("Texture", 2D) = "" {}
    }
    SubShader
    {
        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex VSMain
            #pragma fragment PSMain
            #pragma multi_compile_fwdbase
            #include "AutoLight.cginc"
           
            sampler2D _TextureMap;

            struct SHADERDATA
            {
                float4 position : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 _ShadowCoord : TEXCOORD1;
            };

            float4 ComputeScreenPos (float4 p)
            {
                float4 o = p * 0.5;
                return float4(float2(o.x, o.y*_ProjectionParams.x) + o.w, p.zw);
            }

            SHADERDATA VSMain (float4 vertex:POSITION, float2 uv:TEXCOORD0)
            {
                SHADERDATA vs;
                vs.position = UnityObjectToClipPos(vertex);
                vs.uv = uv;
                vs._ShadowCoord = ComputeScreenPos(vs.position);
                return vs;
            }

            float4 PSMain (SHADERDATA ps) : SV_TARGET
            {
                return lerp(float4(0,0,0,1), tex2D(_TextureMap, ps.uv), step(0.5, SHADOW_ATTENUATION(ps)));
            }
           
            ENDCG
        }
       
        Pass
        {
            Tags{ "LightMode" = "ShadowCaster" }
            CGPROGRAM
            #pragma vertex VSMain
            #pragma fragment PSMain

            float4 VSMain (float4 vertex:POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertex);
            }

            float4 PSMain (float4 vertex:SV_POSITION) : SV_TARGET
            {
                return 0;
            }
           
            ENDCG
        }
    }
}
5 Likes

This works very well, exactly what i needed. Thank you.

How would you change the code to attenuate the shadow?
I tried changing the alpha value on line 44 (in the lerp) without success. The shaddow remains a perfect black.
Thanks in advance.

You’d need to use a shadow caster pass that dithers based on the opacity. Something like what Unity’s Standard shader does.

Catlike Coding has a tutorial on this specific topic:

1 Like

Sorry for the delay: thanks bgolus, I used one of the shaders provided in the tutos and it more or less works. I will keep going with the rest of the game programing and get back to the shader part once everything is done (shaders programing is just not my thing…).

i truggle this for like 10 hour, thank you so much. This work even in the 2023 version of unity

1 Like

I modified the shader to let you change the brightness of the shadow.

Shader"UnlitWithShadows"
{
    Properties
    {
        _TextureMap ("Texture", 2D) = "" {}
        _TileAmount ("Scale Multiplier", float) = 1
        _ShadowBrightness ("Shadow Brightness", Range(0, 1)) = 0
    }
    SubShader
    {
        Pass
        {
            Tags {"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex VSMain
            #pragma fragment PSMain
            #pragma multi_compile_fwdbase
#include "AutoLight.cginc"
         
sampler2D _TextureMap;
float _TileAmount;
float _ShadowBrightness;
struct SHADERDATA
{
    float4 position : SV_POSITION;
    float2 uv : TEXCOORD0;
    float4 _ShadowCoord : TEXCOORD1;
};
float4 ComputeScreenPos(float4 p)
{
    float4 o = p * 0.5;
    return float4(float2(o.x, o.y * _ProjectionParams.x) + o.w, p.zw);
}
SHADERDATA VSMain(float4 vertex : POSITION, float2 uv : TEXCOORD0)
{
    SHADERDATA vs;
    vs.position = UnityObjectToClipPos(vertex);
    vs.uv = uv;
    vs._ShadowCoord = ComputeScreenPos(vs.position);
    return vs;
}
float4 PSMain(SHADERDATA ps) : SV_TARGET
{
    float4 col = tex2D(_TextureMap, ps.uv * _TileAmount);
    return lerp(col * float4(_ShadowBrightness, _ShadowBrightness, _ShadowBrightness, 1), col, step(0.5, SHADOW_ATTENUATION(ps)));
}
         
            ENDCG
        }
     
        Pass
        {
            Tags{ "LightMode" = "ShadowCaster" }
            CGPROGRAM
            #pragma vertex VSMain
            #pragma fragment PSMain
float4 VSMain(float4 vertex : POSITION) : SV_POSITION
{
    return UnityObjectToClipPos(vertex);
}
float4 PSMain(float4 vertex : SV_POSITION) : SV_TARGET
{
    return 0;
}
         
            ENDCG
        }
    }
}
1 Like

Good stuff!

What would the Tags {“LightMode” = “ForwardBase”} in the first pass be in URP?

“LightMode”=“SRPDefaultUnlit”

Unlit URP with shadow casting:

Shader "UnlitURP"
{
    Properties
    {
        _BaseMap ("Texture", 2D) = "white" {}
        [HideInInspector] _Cull("__cull", Float) = 2.0
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "RenderType" = "Opaque"
            "Queue" = "Geometry"
            "UniversalMaterialType" = "Lit"
            "IgnoreProjector" = "True"
        }

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

        CBUFFER_START(UnityPerMaterial)
        float4 _BaseMap_ST;
        float4 _BaseColor;
        float _Cutoff;  
        CBUFFER_END
        ENDHLSL

        Pass
        {
            Name "Unlit"
            Tags { "LightMode"="SRPDefaultUnlit" }
            HLSLPROGRAM
            #pragma vertex VSMain
            #pragma fragment PSMain
            #pragma shader_feature _ALPHATEST_ON

            struct Attributes
            {
                float4 vertex    : POSITION;
                float2 uv        : TEXCOORD0;
                float4 color    : COLOR;
            };

            struct Varyings
            {
                float4 vertex     : SV_POSITION;
                float2 uv        : TEXCOORD0;
                float4 color    : COLOR;
            };
          
            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            Varyings VSMain(Attributes IN)
            {
                Varyings OUT;
                OUT.vertex = TransformObjectToHClip(IN.vertex.xyz);
                OUT.uv = IN.uv;
                OUT.color = IN.color;
                return OUT;
            }

            float4 PSMain(Varyings IN) : SV_Target
            {
                float2 uv = IN.uv.xy  * _BaseMap_ST.xy + _BaseMap_ST.zw;
                return _BaseMap.Sample(sampler_BaseMap, uv);
            }
            ENDHLSL
        }

        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode"="ShadowCaster" }
            ZWrite On
            ZTest LEqual
            ColorMask 0
            Cull[_Cull]

            HLSLPROGRAM
            #pragma vertex ShadowPassVertex
            #pragma fragment ShadowPassFragment
            #pragma shader_feature _ALPHATEST_ON
            #pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma multi_compile_instancing
            #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
            ENDHLSL
        }

        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode"="DepthOnly" }
            ColorMask 0
            ZWrite On
            ZTest LEqual

            HLSLPROGRAM
            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment
            #pragma shader_feature _ALPHATEST_ON
            #pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma multi_compile_instancing
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
        }

        Pass
        {
            Name "DepthNormals"
            Tags { "LightMode"="DepthNormals" }
            ZWrite On
            ZTest LEqual

            HLSLPROGRAM
            #pragma vertex DepthNormalsVertex
            #pragma fragment DepthNormalsFragment
            #pragma shader_feature_local _NORMALMAP
            #pragma shader_feature _ALPHATEST_ON
            #pragma shader_feature _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma multi_compile_instancing
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthNormalsPass.hlsl"
            ENDHLSL
        }
    }
}

Would it be possible for it to receive shadows too?

I’ve been looking all over and could not find a solution. How do I make it receive shadows?