Avoiding overlap in my Glow shader

I made a fairly simple shader based on the normal extrusion example. I’m using it to make objects glow. First of all, here’s what it looks like:

3499701--278920--MeshGlow.gif

This is doing mostly what I want, with the shader growing the mesh. However, I don’t like that any overlapping portions of the mesh get drawn multiple times, resulting in the extra saturated overlap.

I looked into this, searching for “ghost” shader examples. They all recommend a two-pass approach, where the first pass just writes to the z-buffer. But so far none of the additional passes I’ve tries has worked.

Here’s my shader as it works in the GIF above. I tried a first pass vert function, which had the same logic as the vert function I’m showing here, but it doesn’t seem to work the same way in an initial pass as it does in the second pass. Curious if anyone knows how I can avoid the overlapping rendering.

Thanks.

Shader "Gravia/MeshGlow" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Speed ("Speed", Range(0,10)) = 1.5
        _EmissionStrength ("Emission Strength", Range(0,10)) = 1
        _MaxThickness ("Max Thickness", Range(0,2)) = .2
        _MaxOpacity ("Max Opacity", Range(0,1)) = .5
    }
    SubShader {
        Tags {"Queue"="Transparent" "RenderType"="Transparent" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert alpha vertex:vert

        #pragma target 3.0

        #define M_PI 3.1415926535897932384626433832795

        struct Input {
            float2 uv_MainTex;
        };
       
        fixed4 _Color;
        sampler2D _MainTex;
        half _MaxThickness;
        half _MaxOpacity;
        half _EmissionStrength;
        half _Speed;
       
        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Emission = c.rgb * _EmissionStrength;

            half opacity = (sin(_Time.y * _Speed * M_PI) * 0.5 + 0.5) * _MaxOpacity;
            o.Alpha = opacity;
        }

        void vert (inout appdata_full v) {
            half frequency = M_PI * _Speed;

            // This looks a little silly, but the it's based on this calc:
            //  https://docs.google.com/spreadsheets/d/18_hKrlclQRlf9N_XlgSZ0R5TkeLchvE7RSyeGyl8E5o/edit?usp=drive_web&ouid=114159308504791951085
            // I had to replace x % y with (x / y) % 1 because shader logic doesn't allow fractional mod.
            half thicknessModifier = frac((((frequency) * _Time.y + (M_PI / 2)) * _Speed / 2) / (frequency)) / (frequency);


            v.vertex.xyz += normalize(v.normal) * thicknessModifier * _MaxThickness;
        }
        ENDCG

       
    }
    FallBack "Diffuse"
}

I did some more work on this, and found a solution that works. I’m not sure it’s quite right, but it does what I want.

3499889--278943--BetterGlow.gif

Here’s the working shader:

Shader "Gravia/MeshGlow" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _ColorStrength ("Color Strength", Range(0,10)) = 1
        _Speed ("Speed", Range(0,10)) = 1.5
        _MaxThickness ("Max Thickness", Range(0,2)) = .2
        _MaxOpacity ("Max Opacity", Range(0,1)) = .5
    }


    CGINCLUDE
    #include "UnityCG.cginc"
    struct appdata
    {
        float4 vertex : POSITION; // vertex position
        float4 normal : NORMAL; // texture coordinate
        float2 uv : TEXCOORD0; // texture coordinate
    };

    struct v2f
    {
        float2 uv : TEXCOORD0; // texture coordinate
        float4 vertex : SV_POSITION; // clip space position
    };
    v2f ExpansionVert (appdata v, half speed, half maxThickness)
    {
        v2f o;

        // The expansion of the mesh is controlled via this thickness modifier.
        // The function is designed to increase from 0 to 1 as the sine-value proceeds over 
        // a full cycle.
        half thicknessModifier = frac(_Time.y * 0.5 * speed + .25);   
        v.vertex.xyz += normalize(v.normal.xyz) * thicknessModifier * maxThickness;


        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = v.uv;
        return o;
    }
    ENDCG

    SubShader
    {
        Tags {"Queue"="Transparent" "RenderType"="Transparent" }
        LOD 100

        Pass
        {
            ZWrite On
            ColorMask 0

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            half _MaxThickness;
            half _Speed;
           
            v2f vert (appdata v)
            {
                return ExpansionVert(v, _Speed, _MaxThickness);
            }
           
            fixed4 frag (v2f i) : SV_Target
            {
                return (0,0,0,0);
            }
            ENDCG
        }
   

        Pass
        {
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #define M_PI 3.1415926535897932384626433832795

            fixed4 _Color;
            half _MaxThickness;
            half _MaxOpacity;
            half _ColorStrength;
            half _Speed;
           
            v2f vert (appdata v)
            {
                return ExpansionVert(v, _Speed, _MaxThickness);
            }

            // pixel shader, no inputs needed
            fixed4 frag (v2f i) : SV_Target
            {
               
                half opacity = (sin(_Time.y * _Speed * M_PI) * 0.5 + 0.5) * _MaxOpacity;
                fixed4 col =  (_Color * _ColorStrength) * opacity;
                col.a = opacity;
                return col;
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}
3 Likes