Trying to create an additive glow that only illuminates certain layers

Hello! In my 2D platformer I am using a sprite with a custom additive shader to illuminate objects in a simple retro fashion. Currently it has the effect of illuminating the “near” background as well as the “far” background, as demonstrated in this screenshot:

I am seeking to accomplish a similar effect but without illuminating the “far” background, as illustrated in this mockup:

Here is the code for the shader:

Shader "Custom/Additive"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        [HDR]_Color ("Tint", Color) = (1,1,1,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 DstColor DstAlpha

        Pass
        {
            CGPROGRAM
                #pragma vertex SpriteVert
                #pragma fragment SpriteFrag
                #pragma target 2.0
                #pragma multi_compile_instancing
                #pragma multi_compile_local _ PIXELSNAP_ON
                #pragma multi_compile _ ETC1_EXTERNAL_ALPHA
                #include "UnitySprites.cginc"
            ENDCG
        }
    }
}

So far I’ve explored multiple-camera solutions to no avail, and my understanding of shaders is unfortunately very rudimentary. I expect the simplest solution would be to change the sky background shader. The tilemaps and objects in the scene use a modified sprite shader that allows basic pallette swapping, and the sky background uses the default sprite shader. All renderers are on the default Sorting Layer, and arranged using the Order in Layer value. A highly performant solution is especially preferable as I am targeting lower-end hardware.

Thanks!

I don’t know about your shader, but even with official 2d point light, if you move the objects away from the light(Z direction) they should stop receiving the lights after a point because that object ends up not being close enough to the light source. So, just move the background away?

Apologies, I should have specified that I’m using the Built-In Render Pipeline and so I don’t have access to Unity’s 2D lights. My shader is attached to a Sprite Renderer so no true lighting is taking place. I won’t completely rule out switching between pipelines, but I expect it may be more trouble than it’s worth for my year-old project.

I know, your shader is cgprogram which means your project is built-in pipeline

What I meant was, try moving the background sprite’s Z position to see how it effects the lighting. In most cases the lights(even 2d lights) calculate the distance using the XYZ coordinates instead of using just the XY coordinates

LOL, I didn’t see this so ignore my above comment.

Why can’t you use another material for the background?

There isn’t anything fancy going on with the background so I can certainly use another material, I just haven’t figured out what would work. I’m not bound to the default sprite shader I’m currently using.

This is just a simple shader update then.

Find the SpriteFrag method(where ever it is); it’ll have some way of multiplying the color of the fragment(pixel) color with light color. You can then just create another shader without the fragment light calculations and use it for background

Possibly the light calculations could be on the vertex shader instead, you’ll just have to find where exactly light calculations are done

we might be able to help more if you can post the actual shaders(dont post it if it isnt open source etc.)

PS: for most performance, remove useless calculations from vertex and fragment shaders. PS2:compiler might remove them when compiling even if you dont

Alright, I’ve made some progress here with a different approach by drawing my sprites to a stencil buffer and only drawing the light over the stencil. The only issue now is that the PixelColors shader I use for pallette swapping doesn’t clip transparent pixels, so the invisible areas of my sprites are illuminated like this:

I attempted to add alpha clipping to the shader but everything I tried broke it completely. I’m sure it wouldn’t be overly complex to accomplish but I have run up against the limits of my understanding of shaders.

Here’s the code for the shader I’m using on pretty much everything I want to be illuminated:

Shader "Custom/PixelColors" {
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _ColorTint ("Tint", Color) = (1,1,1,1)
        _Color1in ("Color 1 In", Color) = (1,1,1,1)
        _Color1out ("Color 1 Out", Color) = (1,1,1,1)
        _Color2in ("Color 2 In", Color) = (1,1,1,1)
        _Color2out ("Color 2 Out", Color) = (1,1,1,1)
        _Color3in ("Color 3 In", Color) = (1,1,1,1)
        _Color3out ("Color 3 Out", Color) = (1,1,1,1)
        _Color4in ("Color 4 In", Color) = (1,1,1,1)
        _Color4out ("Color 4 Out", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    }
    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }
        Cull Off
        Lighting Off
        ZWrite Off
        Fog { Mode Off }
        Blend SrcAlpha OneMinusSrcAlpha

        Stencil {
            Ref 1
            Comp Always
            Pass Replace
            }
        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag      
            #pragma multi_compile DUMMY PIXELSNAP_ON
            #include "UnityCG.cginc"
       
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };
            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                half2 texcoord  : TEXCOORD0;
            };
       
            fixed4 _ColorTint;
            fixed4 _Color1in;
            fixed4 _Color1out;
            fixed4 _Color2in;
            fixed4 _Color2out;
            fixed4 _Color3in;
            fixed4 _Color3out;
            fixed4 _Color4in;
            fixed4 _Color4out;
            v2f vert(appdata_t IN)
            {
                v2f OUT;
                OUT.vertex = UnityObjectToClipPos(IN.vertex);
                OUT.texcoord = IN.texcoord;        
                OUT.color = IN.color * _ColorTint;
                #ifdef PIXELSNAP_ON
                OUT.vertex = UnityPixelSnap (OUT.vertex);
                #endif
                return OUT;
            }
            sampler2D _MainTex;    

            fixed4 frag(v2f IN) : COLOR
            {
                float4 texColor = tex2D( _MainTex, IN.texcoord );
                texColor = all(texColor == _Color1in) ? _Color1out : texColor;
                texColor = all(texColor == _Color2in) ? _Color2out : texColor;
                texColor = all(texColor == _Color3in) ? _Color3out : texColor;
                texColor = all(texColor == _Color4in) ? _Color4out : texColor;
             
                return texColor * IN.color;
            }
        ENDCG
        }
    }
}

Do you think you could modify this shader so that it discards transparent pixels before they are added to the stencil?

Alpha clipping should work :thinking:

in the fragment shader, after float4 texColor = tex2D( _MainTex, IN.texcoord ); do something like

clip(texColor .a - alphaCutoffValue);

alphaCutoffValue can be a property or a constant; I don’t remember if alpha range was between 0-1 or 0-255; try both!

2 Likes

Oh btw they say clip is notorious for “bad performance”. Setting the alpha of texColor to 0 instead of clipping was a recommendation, it obviously wont work if material is not transparent

That worked perfectly. Performance doesn’t seem to have taken a hit either. I’ll figure out shaders one of these days. Thank you very much for all your help!

1 Like