- Home /
2D drop shadows with faded edges and no additive alpha blending
For my top down 2D game, I want to add drop shadows to certain gameobjects. Each of these objects is to be given a child 'Shadow' gameobject which would simply have the same sprite in semi-transparent grey and with faded edges. Ideally, this would all be handled by a shader applied to the shadow sprite which would set the colour, fade the edges, and prevent the shadows being too dark in overlaps (due to additive alpha blending).
For my first attempt at this, I created a shader which sets the semi-transparent grey colour and uses a stencil buffer to handle overlaps, though I do not know how to fade the edges in a shader.
To add these in for my second attempt, I created a drop shadow version of each sprite with faded edges. However, applying a shader with a similar stencil buffer to these new sprites of course means that the faded out portion (of the first shadow rendered) is drawn through the overlap (as depicted below).
This image shows two overlapping shadows produced by my first two attempts:
For clarity, here is what I want to achieve:
I need a shader please that does not simply discard pixels from the second shadow in overlaps, but keeps whichever pixel has the highest alpha value (my understanding is that BlendOp Max
won't work here because the first shadow will have already been blended with the opaque background before the second shadow is processed). If that shader could handle fading the edges too (so I don't have to create a second sprite each time), then that's a bonus. Or maybe a different approach altogether is required? Many thanks.
Answer by MatthewOCallaghan · Aug 28, 2020 at 01:23 PM
I have since managed to achieve the effect I want but it is not the best solution.
For the faded edges, I am still creating a version of each sprite with faded edges to use.
I then apply a shader to this which uses three passes:
The first pass sets the pixel alpha to the alpha value of the first overlapping shadow. This is because the destination alpha will always be 1 due to the opaque background and I need to ditch this for the second pass to work. A stencil buffer is used to stop this first pass running for more than one shadow.
The second pass uses
BlendOp Max
to set the pixel alpha value to the darkest shadow. Had I not have scrapped the destination alpha of 1 in the previous pass, the max operation here would have always returned 1 (that's why the first pass is required). Both of these first two passes have a colour mask that only writes to the alpha channel.Finally, the third pass blends a specified shadow colour (in my case grey) with the destination colour using
Blend DstAlpha OneMinusDstAlpha
. This means the shadow effectively has the alpha that we calculated to be the largest of the overlapping shadows.
The code for this shader is as follows:
// Adapted from explanation at https://www.reddit.com/r/Unity3D/comments/20jh1n/overlapping_opacity_shadow/
Shader "Custom/ShadowShader3"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_Colour("Colour", Color) = (0,0,0,1)
}
SubShader
{
Tags
{
"Queue" = "Transparent"
"IgnoreProjector" = "True"
"RenderType" = "TransparentCutout"
"PreviewType" = "Plane"
"CanUseSpriteAtlas" = "True"
}
Cull Off
Lighting Off
ZWrite Off
Pass // Set alpha to first shadow
{
ColorMask A
Blend One Zero
Stencil
{
Ref 3
Comp NotEqual
Pass Replace
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
half4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
half4 frag(v2f i) : SV_TARGET
{
fixed4 colour = tex2D(_MainTex, i.uv);
return colour;
}
ENDCG
}
Pass // Find max shadow alpha and set pixel alpha to that
{
ColorMask A
BlendOp Max
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
half4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
half4 frag(v2f i) : SV_TARGET
{
fixed4 colour = tex2D(_MainTex, i.uv);
return colour;
}
ENDCG
}
Pass // Apply colour to pixel in the amount of current alpha and background will be (1 - current alpha)
{
Stencil // Only needs to happen once
{
Ref 4
Comp NotEqual
Pass Replace
}
Blend DstAlpha OneMinusDstAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
half4 pos : SV_POSITION;
};
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 _Colour;
half4 frag(v2f i) : SV_TARGET
{
return _Colour;
}
ENDCG
}
}
Fallback off
}
As I say, this must be far from the best solution and I would love to include the fading in the shader so please suggest alternatives.
Your answer
Follow this Question
Related Questions
Implementing Dynamic Shadows on iOS in openGL Es 2.0 0 Answers
TransparencyLM - Colored Shadows 0 Answers
Weapon / Viewmodel clipping shader methods 0 Answers
shader forge mesh alpha blending issue 0 Answers