Shader "Bansonic/Sprite Outline" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} [HideInInspector] _Color ("Tint", Color) = (1,1,1,1) [HideInInspector] PixelSnap ("Pixel snap", Float) = 0 [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1) [HideInInspector] _AlphaTex ("External Alpha", 2D) = "white" {} [HideInInspector] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 [HDR]_OutlineColor ("Outline Color", Color) = (1,1,1,1) _OutlineThickness ("Outline Thickness", Range(0, 12)) = 1 _AlphaThreshold ("Alpha Threshold", Range(0, 1)) = 0.1 _OutlineOpacity ("Outline Opacity", Range(0, 1)) = 1 _InnerOutline ("Inner Outline Blend", Range(0, 1)) = 0 _SpriteAlpha ("Sprite Alpha", Range(0, 1)) = 1 } SubShader { Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "RenderPipeline" = "UniversalPipeline" "CanUseSpriteAtlas" = "True" } Blend SrcAlpha OneMinusSrcAlpha, One OneMinusSrcAlpha Cull Off ZWrite Off Pass { Tags { "LightMode" = "Universal2D" } HLSLPROGRAM #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/2D/Include/Core2D.hlsl" #pragma vertex SpriteVertex #pragma fragment SpriteFragment #pragma target 2.0 #pragma multi_compile_instancing #pragma multi_compile _ SKINNED_SPRITE struct Attributes { float3 positionOS : POSITION; float4 color : COLOR; float2 uv : TEXCOORD0; UNITY_SKINNED_VERTEX_INPUTS UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; half4 color : COLOR; float2 uv : TEXCOORD0; UNITY_VERTEX_OUTPUT_STEREO }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); float4 _MainTex_TexelSize; CBUFFER_START(UnityPerMaterial) half4 _Color; half4 _OutlineColor; half _OutlineThickness; half _AlphaThreshold; half _OutlineOpacity; half _InnerOutline; half _SpriteAlpha; CBUFFER_END Varyings SpriteVertex(Attributes input) { Varyings output = (Varyings)0; UNITY_SETUP_INSTANCE_ID(input); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); UNITY_SKINNED_VERTEX_COMPUTE(input); SetUpSpriteInstanceProperties(); input.positionOS = UnityFlipSprite(input.positionOS, unity_SpriteProps.xy); output.positionCS = TransformObjectToHClip(input.positionOS); output.uv = input.uv; output.color = input.color * _Color * unity_SpriteColor; return output; } half SampleAlpha(float2 uv, half4 tint) { half4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv) * tint; return tex.a; } half ComputeOuterMask(float2 uv, half4 tint) { float2 texel = _MainTex_TexelSize.xy * max(_OutlineThickness, 0.001h); half center = SampleAlpha(uv, tint); half maxNeighbor = 0h; maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 1.0, 0.0), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2(-1.0, 0.0), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 0.0, 1.0), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 0.0, -1.0), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 0.7071, 0.7071), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2(-0.7071, 0.7071), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 0.7071, -0.7071), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2(-0.7071, -0.7071), tint)); half centerMask = step(_AlphaThreshold, center); half neighborMask = step(_AlphaThreshold, maxNeighbor); return saturate(neighborMask - centerMask); } half ComputeInnerMask(float2 uv, half4 tint) { float2 texel = _MainTex_TexelSize.xy * max(_OutlineThickness, 0.001h); half center = SampleAlpha(uv, tint); half minNeighbor = 1h; minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 1.0, 0.0), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2(-1.0, 0.0), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 0.0, 1.0), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 0.0, -1.0), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 0.7071, 0.7071), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2(-0.7071, 0.7071), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 0.7071, -0.7071), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2(-0.7071, -0.7071), tint)); half centerMask = step(_AlphaThreshold, center); half neighborMask = step(_AlphaThreshold, minNeighbor); return saturate(centerMask - neighborMask); } half4 SpriteFragment(Varyings input) : SV_Target { half4 baseSample = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv) * input.color; half outerMask = ComputeOuterMask(input.uv, input.color); half innerMask = ComputeInnerMask(input.uv, input.color); half outlineMask = saturate(lerp(outerMask, max(outerMask, innerMask), _InnerOutline)) * _OutlineOpacity; half4 color = baseSample; color.rgb *= _SpriteAlpha; color.a *= _SpriteAlpha; color.rgb = lerp(color.rgb, _OutlineColor.rgb, outlineMask * _OutlineColor.a); color.a = saturate(color.a + outlineMask * _OutlineColor.a); return color; } ENDHLSL } Pass { Tags { "LightMode" = "UniversalForward" } HLSLPROGRAM #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/Shaders/2D/Include/Core2D.hlsl" #pragma vertex SpriteVertex #pragma fragment SpriteFragment #pragma target 2.0 #pragma multi_compile_instancing #pragma multi_compile _ SKINNED_SPRITE struct Attributes { float3 positionOS : POSITION; float4 color : COLOR; float2 uv : TEXCOORD0; UNITY_SKINNED_VERTEX_INPUTS UNITY_VERTEX_INPUT_INSTANCE_ID }; struct Varyings { float4 positionCS : SV_POSITION; half4 color : COLOR; float2 uv : TEXCOORD0; UNITY_VERTEX_OUTPUT_STEREO }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); float4 _MainTex_TexelSize; CBUFFER_START(UnityPerMaterial) half4 _Color; half4 _OutlineColor; half _OutlineThickness; half _AlphaThreshold; half _OutlineOpacity; half _InnerOutline; half _SpriteAlpha; CBUFFER_END Varyings SpriteVertex(Attributes input) { Varyings output = (Varyings)0; UNITY_SETUP_INSTANCE_ID(input); UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); UNITY_SKINNED_VERTEX_COMPUTE(input); SetUpSpriteInstanceProperties(); input.positionOS = UnityFlipSprite(input.positionOS, unity_SpriteProps.xy); output.positionCS = TransformObjectToHClip(input.positionOS); output.uv = input.uv; output.color = input.color * _Color * unity_SpriteColor; return output; } half SampleAlpha(float2 uv, half4 tint) { half4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv) * tint; return tex.a; } half ComputeOuterMask(float2 uv, half4 tint) { float2 texel = _MainTex_TexelSize.xy * max(_OutlineThickness, 0.001h); half center = SampleAlpha(uv, tint); half maxNeighbor = 0h; maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 1.0, 0.0), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2(-1.0, 0.0), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 0.0, 1.0), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 0.0, -1.0), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 0.7071, 0.7071), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2(-0.7071, 0.7071), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2( 0.7071, -0.7071), tint)); maxNeighbor = max(maxNeighbor, SampleAlpha(uv + texel * float2(-0.7071, -0.7071), tint)); half centerMask = step(_AlphaThreshold, center); half neighborMask = step(_AlphaThreshold, maxNeighbor); return saturate(neighborMask - centerMask); } half ComputeInnerMask(float2 uv, half4 tint) { float2 texel = _MainTex_TexelSize.xy * max(_OutlineThickness, 0.001h); half center = SampleAlpha(uv, tint); half minNeighbor = 1h; minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 1.0, 0.0), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2(-1.0, 0.0), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 0.0, 1.0), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 0.0, -1.0), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 0.7071, 0.7071), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2(-0.7071, 0.7071), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2( 0.7071, -0.7071), tint)); minNeighbor = min(minNeighbor, SampleAlpha(uv + texel * float2(-0.7071, -0.7071), tint)); half centerMask = step(_AlphaThreshold, center); half neighborMask = step(_AlphaThreshold, minNeighbor); return saturate(centerMask - neighborMask); } half4 SpriteFragment(Varyings input) : SV_Target { half4 baseSample = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv) * input.color; half outerMask = ComputeOuterMask(input.uv, input.color); half innerMask = ComputeInnerMask(input.uv, input.color); half outlineMask = saturate(lerp(outerMask, max(outerMask, innerMask), _InnerOutline)) * _OutlineOpacity; half4 color = baseSample; color.rgb *= _SpriteAlpha; color.a *= _SpriteAlpha; color.rgb = lerp(color.rgb, _OutlineColor.rgb, outlineMask * _OutlineColor.a); color.a = saturate(color.a + outlineMask * _OutlineColor.a); return color; } ENDHLSL } } FallBack "Sprites/Default" }