Shader "Custom/OutlineUnlit" { Properties { _MainColor ("Main Color", Color) = (0.1, 0.3, 0.8, 0.3) _OutlineColor ("Outline Color", Color) = (0.2, 0.5, 1, 1) _OutlineWidth ("Outline Width", Range(0, 10)) = 2.0 } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off // Pass 1: clip-space outline (screen-pixel width, scale-independent) Pass { Name "OUTLINE" Cull Front ZTest Always CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" float _OutlineWidth; float4 _OutlineColor; struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); // transform normal to clip space float3 clipNormal = mul((float3x3)UNITY_MATRIX_VP, mul((float3x3)unity_ObjectToWorld, v.normal)); // offset in clip space by a fixed pixel width float2 offset = normalize(clipNormal.xy) * _OutlineWidth * 0.001; // aspect ratio correction offset.x /= _ScreenParams.x / _ScreenParams.y; o.pos.xy += offset * o.pos.w; return o; } fixed4 frag(v2f i) : SV_Target { return _OutlineColor; } ENDCG } // Pass 2: inner fill Pass { Name "FILL" Cull Back CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" float4 _MainColor; struct appdata { float4 vertex : POSITION; }; struct v2f { float4 pos : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag(v2f i) : SV_Target { return _MainColor; } ENDCG } } }