Shader to create an outline inside a polygon

For others who might find this thread I’ll post the approach I now use to handle this problem.

  1. I calculate the vectors I use for scaling the polygon correctly (red arrows in the screenshot)
  2. I pass them as tangents of the vertices in the polygon mesh.
  3. I created a two-pass shader that:
  • In a first pass draws the polygon normally in the color that the outline should have.
  • In a second pass I scale the polygon down along the tangent vectors and draw it in the color the polygon should have.
  1. The result is an outline on the inside of any polygon, where I can dynamically and performantly change the outline color and width through the shader which is what I wanted.

The shader code (“Border” is what I called the “Outline”). The axis might be a bit confusing, this is because the polygons I work with are in 3D-Space on the xz-plane (like a floor).

Shader "Custom/DistrictShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color("Color", Color) = (1,1,1,1)

        _BorderColor("Border Color", Color) = (0,0,0,1)
        _BorderWidth("Border Width", Range(0,0.2)) = 0.02

        _Blink("Blink", Float) = 0
        _BlinkDuration("Blink Duration", Float) = 2
        _BlinkColor("Blink Color", Color) = (1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        // 1. Pass: Paint normally in border color
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

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

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

            float4 _BorderColor;

            // Build the object (goes through each vertex)
            v2f vert (appdata IN)
            {
                v2f OUT;
                OUT.vertex = UnityObjectToClipPos(IN.vertex);
                OUT.uv = IN.uv;
                return OUT;
            }

            // Draw in the object (goes through each pixel)
            fixed4 frag (v2f IN) : SV_Target
            {
                return _BorderColor;
            }
            ENDCG
        }

       
        // 2. Pass: Shrink in tangent direction and paint normally
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float4 tangent: TANGENT;
                float2 uv : TEXCOORD0;
            };

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

            sampler2D _MainTex;
            float _BorderWidth;
            float4 _Color;
            float3 Offset = float3(0, 0, 0);
            float _Blink;
            float _BlinkDuration;
            float3 _BlinkColor;

            // Build the object (goes through each vertex)
            v2f vert(appdata IN)
            {
                v2f OUT;
                float3 ver = IN.vertex.xyz;
                ver = ver + (_BorderWidth * IN.tangent) + float3(0, 0.001, 0);
                OUT.vertex = UnityObjectToClipPos(ver);
                OUT.uv = IN.uv;
                return OUT;
            }

            // Draw in the object (goes through each pixel)
            fixed4 frag(v2f IN) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, IN.uv);
                col = col * _Color;

                if (_Blink == 1.0f)
                {
                    float r = (0.5f + abs(sin(_Time.w * (1 / _BlinkDuration))));
                    col *= r;
                }
                return col;
            }
            ENDCG
        }
    }
}