Forward Add Pass with multiple stacked sprites

I have a problem with the forwardAdd pass.

Compared to the base pass, it seems the forward add pass lights up to much, in other words, it light up the sprites more than their original color.

Another issue is when I have stacked sprites. Then the forward add pass adds the light from all objects I guess to the top one. Making it very bright.

I feel there is something Im doing wrong with the forward add pass.

Picture of issue is attached.

Shader:

Shader "Custom/TestShader2"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Name "FORWARD"
        Tags {
            "LightMode" = "ForwardBase"
            "Queue"="Transparent"
            "RenderType"="Transparent"
        }
 
        Pass
        {
            Cull Off
            ZWrite Off
            Blend One OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #pragma multi_compile_fwdbase

            #include "UnityCG.cginc"
            uniform float4 _LightColor0;

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                half3 normal : NORMAL;
            };

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

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            float3 Shade4Lights (
                float4 lightPosX, float4 lightPosY, float4 lightPosZ,
                float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,
                float4 lightAttenSq,
                float3 pos)
            {
   
                // to light vectors
                float4 toLightX = lightPosX - pos.x;
                float4 toLightY = lightPosY - pos.y;
                float4 toLightZ = lightPosZ - pos.z;
                // squared lengths
                float4 lengthSq = 0;
                lengthSq += toLightX * toLightX;
                lengthSq += toLightY * toLightY;
                lengthSq += toLightZ * toLightZ;
                // don't produce NaNs if some vertex position overlaps with the light
                lengthSq = max(lengthSq, 0.000001);

                // attenuation
                float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
                float4 diff = atten; //ndotl * atten;
                // final color
                float3 col = 0;
                col += lightColor0 * diff.x;
                col += lightColor1 * diff.y;
                col += lightColor2 * diff.z;
                col += lightColor3 * diff.w;
                return col;
            }

            half3 GetAmbientLight()
            {
                return _LightColor0.rgb;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                half3 light = Shade4Lights(unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, unity_LightColor[0], unity_LightColor[1], unity_LightColor[2], unity_LightColor[3], unity_4LightAtten0, i.worldPos);
                half3 ambient = GetAmbientLight();
                light = min((half3)1, ambient + light);

                col.rgb *= light;

                clip(col.a - 0.5);
                return col;
            }
            ENDCG
        }

        Pass {
            Tags { "LightMode" = "ForwardAdd" }
            Cull Off
            ZWrite Off
            Blend One One
            CGPROGRAM
            #pragma multi_compile_fwdadd
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            uniform float4 _LightColor0;

            struct vertexInput {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct vertexOutput {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 posWorld : TEXCOORD1;
                float3 normalDir : TEXCOORD2;
                LIGHTING_COORDS(3,4)
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
     
            vertexOutput vert(vertexInput v)
            {
                vertexOutput output;
                output.uv = TRANSFORM_TEX(v.uv, _MainTex);

                float4x4 modelMatrix = unity_ObjectToWorld;
                float4x4 modelMatrixInverse = unity_WorldToObject;
                output.posWorld = mul(modelMatrix, v.vertex);
                output.normalDir = normalize(
                    mul(float4(v.normal, 0.0), modelMatrixInverse).xyz);
                output.pos = UnityObjectToClipPos(v.vertex);
                TRANSFER_VERTEX_TO_FRAGMENT(output);
                return output;
            }

            #ifdef POINT
            #define LIGHT_ATTENUATION_MOD(a)    (tex2D(_LightTexture0, dot(a._LightCoord,a._LightCoord).rr).r)
            #endif

            float4 frag(vertexOutput input) : COLOR
            {
                float attenuation;
                if (0.0 == _WorldSpaceLightPos0.w) // directional light?
                {
                    attenuation = 1.0;
                }
                else // point or spot light
                {
                    float2 vertexToLightSource =
                        _WorldSpaceLightPos0.xy - input.posWorld.xy;
                    float distance = length(vertexToLightSource);
                    distance = max(distance, 0.000001);
                    attenuation = LIGHT_ATTENUATION(input) / 2;
                }

                float3 diffuseLight = attenuation * _LightColor0.rgb;
        
                fixed4 col = tex2D(_MainTex, input.uv);
                col.rgb *= diffuseLight;
                clip(col.a - 0.5);
                return col;
            }

            ENDCG
        }
    }
}

7436963--911333--issue.png

Nothing is wrong with your shader, and there’s no work around that allows you to use a forward add pass with transparent sprites.

This is a problem with any kind of multi-pass shader used on overlapping transparent sprites. The “solution” is to not use transparent sprites. You would need to change the base pass to use ZWrite On, and separate all sprites in the Z depth.

Or delete the add pass and stick with only using the base pass.

Or switch to the URP 2D renderer which doesn’t use a multi-pass model for handling multiple lights and the base pass handles all lights in a single pass.

hi, thank you so much for the reply!

My sprites are only transparent where there is no colour. All coloured pixels are full alpha. Is this still a transparent sprite?

In the picture above, I just had one point light set to important, running it in the add pass. If I switch to non important, running the base pass, the light looks perfect.

Attached picture of how it looks if I change to “not-important” light. And this is how I want it to look basically. But I want to use Important light because I dont want to be limited to 4 lights…

Is it still no possible to fix except for the workarounds you mentioned?

br

7437413--911459--issue.png

If you have ZWrite Off and any Blend but Blend Off or Blend One Zero (which is the same as Off) for the base pass, it’s a transparent shader. Technically the problem will happen with opaque shaders that are on all on the same plane too, but that’s why you need both an opaque shader and the depth separation.

Thanks!

Hm this is unfortunate… maybe I will try to just make sure I don’t stack sprites like this and this issue will not be visible. Will have to think about it.

Thanks again, this has been very helpful.

I just found out that this visual issue only happens if the sprites are on the exact same y value. If I change it slightly, the light is no longer “lighting through”. So I can work around it that way.