Sprite Diffuse Shadow Shader with correct blending

Hi,

I’m trying to get the following shader to give the desired effect (sprite borders added for visualisation):


There are numerous similar topics that suggest the use of stencil with Comp NotEqual and Pass Replace, but instead of desired effect I’m getting one where fully transparent regions of one sprite kill the non-transparent ones of another.

I tried various combinations of Comp (Greater, NotEqual) and Pass / Fail (Replace, IncrSat, IncrWrap) - nothing gives desired result.

Here is the full shader code:

Shader "Sprites/Shadow"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        _OffsetX ("Offset X", Float) = 3
        _OffsetY ("Offset Y", Float) = 2
        _Height ("Height", Float) = 1
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
        [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
        [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
        [PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
        [PerRendererData] _EnableExternalAlpha ("Enable External Alpha", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off

        Blend One OneMinusSrcAlpha

        Stencil
            {
            Ref 1
            Comp NotEqual
            Pass Replace
            Fail Keep
            ReadMask 1
            WriteMask 1
        }

        CGPROGRAM
        #pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing
        #pragma multi_compile _ PIXELSNAP_ON
        #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
        #include "UnitySprites.cginc"

        float _OffsetX;
        float _OffsetY;
        float _Height;

        struct Input
        {
            float2 uv_MainTex;
            fixed4 color;
        };

        void vert (inout appdata_full v, out Input o)
        {
            v.vertex = mul(unity_ObjectToWorld, v.vertex);

            v.vertex.x += _OffsetX * _Height;
            v.vertex.y += _OffsetY * _Height;

            v.vertex = mul(unity_WorldToObject, v.vertex);

            v.vertex = UnityFlipSprite(v.vertex, _Flip);

            #if defined(PIXELSNAP_ON)
            v.vertex = UnityPixelSnap (v.vertex);
            #endif

            UNITY_INITIALIZE_OUTPUT(Input, o);
            o.color = v.color * _Color * _RendererColor;
        }

        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = SampleSpriteTexture (IN.uv_MainTex) * IN.color;
            o.Albedo = c.rgb * c.a;
            o.Alpha = c.a;
        }
        ENDCG
    }

Fallback "Transparent/VertexLit"
}

Any ideas what I could do? Also, I don’t want to use clip() or if statements in the code, I am sure there should be a nice way to get the desired effect using Stencil.

You can’t do what you want with stencils.

Stencils are written to anywhere the mesh renders. Just because a mesh is invisible because of alpha blending doesn’t mean it’s not being rendered, the way stencils work by default it’s reading & writing to the stencils before the fragment shader / surface function has even run so it has no idea the pixels aren’t visible.

To avoid that you have to use clip() to skip rendering of those pixels. If you use clip() the GPU will know to wait until after rendering the fragment shader / surface function before writing to the stencil buffer.

But the stencil value you’re writing will always either be a fixed value or not writing anything, there’s no sense of “soft edges” with stencils. It’s by design a binary on/off thing.

Your options are to use something like BlendOp Min, or render out a b&w mask to a render texture, or get creative with destination alpha.

1 Like