Alpha not working on outline shader

Hello everyone!

I am trying to achieve a simple outline shader and have been pulling my hair out over this one problem for a couple of days. I haven’t done graphics programming in a number of years, and never worked with hlsl before this, so I am a little over my head! I mainly adapted this code from Unity Shaderlab: Object Outlines | Will Weissman - Game Developer as well as a sprinkling of some others


Symbol and card in scene view (artwork temporary ;)) Both should be rendered with an outline.

The intended effect is for the card or symbol to have an outline glow when moused over by the user. This currently works to a degree, as shown below. (The mouse over part is not a problem, this has already been extensively tested and I know it works as it triggers an animation, and the problem persists when this is disabled)

6079998--659760--upload_2020-7-11_20-14-24.png
6079998--659763--upload_2020-7-11_20-18-29.png
How it currently looks. This is not correct, it should be following the shape of the symbol.

The method I used, as adapted from the code linked above, was to render the object on a solid colour on a black texture and blur it out, finally colouring it and combining it with the rest of the scene. When I mouse over the untextured quad, I get a nice outline. The slight curve around the corners is created by the blurring. However, I want the outline to follow the shape of the symbol, and also (when I implement it) the curved edges of the cards. For this, I added an alpha channel to my fragment shader.

6079998--659769--upload_2020-7-11_20-28-38.png
The way that Unity itself highlights the symbol in the scene viewer is effectively the desired result of my shaders.

This is when things went wrong. It seems that if I try to input a texture with an alpha channel, and sample it, it won’t draw any outline, let alone the outline of the quad.

So first is the “Single Colour” shader that renders an object as white on a background of black. In the fragment shader, you can see where I tried to implement the alpha channel. I have not found an easy way to output this channel for debugging, so it’s hard to tell in which shader the problem lies.

Shader "Custom/SingleColour"
{
    Properties
    {
        _MainTex("Main Texture", 2D) = ""{}
    }
        SubShader
    {
        Tags {"Queue" = "Transparent" "RenderType" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        Pass
        {
            CGPROGRAM
            sampler2D _MainTex;
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            v2f_img vert(appdata_base v) // An extremely simple vertex shader that returns the position with no changes
            {
                v2f_img o;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f_img i) : SV_Target // Outputs the fragment as a block white colour on the black background, to later be blurred and become an outline
            {
                float alpha = tex2D(_MainTex, i.uv.xy).a;
                return fixed4(1,1,1,alpha);
            }
            ENDCG
        } // End Pass
    } // End Subshader
}

The outline shader, which takes the data from the previous, blurs it, and subtracts the initial texture from the blurred one. It then adds these colours over the main scene render.

Shader "Custom/Outline"
{
    Properties
    {
        _MainTex("Main Texture",2D) = "black"{}
        _SceneTex("Scene Texture",2D) = "black"{}
        _Colour("Outline Colour", Color) = (0, 0, 0, 0)
        //_BlurCycles("Blur Cycles", Int) = 0
    }
        SubShader
    {
        Pass
        {
            CGPROGRAM
            sampler2D _MainTex;
            sampler2D _SceneTex;
            half4 _Colour;
            //int _BlurCycles;
            float2 _MainTex_TexelSize;
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct v2f
            {
                float4 pos : SV_POSITION;
                float2 uvs : TEXCOORD0;
            };

            v2f vert(appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uvs = o.pos.xy / 2 + 0.5;
                return o;
            }

            half4 frag(v2f i) : COLOR
            {
                // Discard the original shape so only an outline remains

                if (tex2D(_MainTex,i.uvs.xy).r > 0)
                {
                    return tex2D(_SceneTex,i.uvs.xy);
                }

                int blurCycles = 15; // Remove and replace all instances with _BlurCycles when fixed

                float TX_x = _MainTex_TexelSize.x;
                float TX_y = _MainTex_TexelSize.y;

                float ColorIntensityInRadius = 0;

                // Blur the outline
                for (int k = 0; k < blurCycles; k += 1)
                {
                    for (int j = 0; j < blurCycles; j += 1)
                    {
                        ColorIntensityInRadius +=
                            (tex2D( _MainTex, i.uvs.xy + float2 ((k - blurCycles / 2) * TX_x, (j - blurCycles / 2) * TX_y)).r);
                    }
                }

                ColorIntensityInRadius *= 0.05;

                half4 color = tex2D(_SceneTex,i.uvs.xy) + ColorIntensityInRadius * _Colour;

                return color;
            }

                ENDCG

            }
        //end pass    
    }
        //end subshader
}
//end shader

This script goes on the camera, and controls the whole process.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RenderOutline : MonoBehaviour
{
    public Shader drawAsColour;
    public Shader outline;
    public Material outlineMaterial;
    Camera tempCam;
    private void Start()
    {
        tempCam = new GameObject().AddComponent<Camera>();
    }
    void OnRenderImage(RenderTexture _source, RenderTexture _destination)
    {
        tempCam.CopyFrom(Camera.current);
        tempCam.backgroundColor = Color.black;
        tempCam.clearFlags = CameraClearFlags.Color;

        tempCam.cullingMask = 1 << LayerMask.NameToLayer("Outline");

        RenderTexture renderTexture = RenderTexture.GetTemporary(_source.width, _source.height, 0, RenderTextureFormat.R8);
        tempCam.targetTexture = renderTexture;

        tempCam.RenderWithShader(drawAsColour, "");
        outlineMaterial.SetTexture("_SceneTex", _source);

        Graphics.Blit(renderTexture, _destination, outlineMaterial);
        RenderTexture.ReleaseTemporary(renderTexture);
    }
}

I hope this is formatted correctly and everything. Thank you in advance to anybody who takes the time out to look over this for me. I really appreciate it.

Resolved! After figuring out how to output a texture to a material I quickly realised the texture coordinates were where the problem lied, as I was not setting them properly in the vertex part of the first shader, and using the built in structs was not wise. I feel like I’ve learned a lot about hlsl solving this! I shall upload the code once I get to my desktop to access it if anybody requires.