Shader "Custom/2D/OutlineShape" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} [Header(Base Settings)] [MainColor] [HDR] _BaseColor ("Base Color (本身颜色)", Color) = (1,1,1,1) [Range(0, 1)] _BaseAlpha ("Base Alpha (本体透明度)", Float) = 1.0 [Space(10)] [Header(Outline Settings)] [Toggle(_OUTLINE_ON)] _EnableOutline("Enable Outline (启用描边)", Float) = 1 [HDR] _OutlineColor ("Outline Color (描边颜色)", Color) = (1, 1, 0, 1) [Range(0, 50)] _OutlineWidth ("Outline Width (描边粗细 像素)", Float) = 5.0 [Range(0.01, 1)] _AlphaThreshold ("Inner Threshold (内描边判定阈值:寻找边界)", Float) = 0.5 [Range(0.01, 1)] _OutlineSmoothness ("Outline Smoothness (描边平滑度:抗锯齿)", Float) = 0.2 // Below properties are required for SpriteRenderer internal data passing and ETC1 support [HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1) [HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1) [HideInInspector] _AlphaTex ("External Alpha", 2D) = "white" {} [HideInInspector] _EnableExternalAlpha ("Enable External Alpha", Float) = 0 } SubShader { Tags { "RenderPipeline" = "UniversalPipeline" "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "True" } Cull Off Lighting Off ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { Name "SpriteOutline" HLSLPROGRAM #pragma target 2.0 #pragma vertex vert #pragma fragment frag #pragma multi_compile_local _ ETC1_EXTERNAL_ALPHA // 新增: 基于本地关键字的着色器变体,如果关闭描边,将彻底剔除描边代码 #pragma shader_feature_local_fragment _OUTLINE_ON #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float3 positionOS : POSITION; float4 color : COLOR; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; half4 color : COLOR; }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); float4 _MainTex_TexelSize; #if defined(ETC1_EXTERNAL_ALPHA) TEXTURE2D(_AlphaTex); SAMPLER(sampler_AlphaTex); #endif CBUFFER_START(UnityPerMaterial) half4 _BaseColor; half _BaseAlpha; half4 _OutlineColor; float _OutlineWidth; float _AlphaThreshold; float _OutlineSmoothness; half4 _RendererColor; float4 _Flip; float _EnableExternalAlpha; CBUFFER_END Varyings vert(Attributes IN) { Varyings OUT; float3 pos = IN.positionOS; pos.xy *= _Flip.xy; OUT.positionCS = TransformObjectToHClip(pos); OUT.uv = IN.uv; // 注意这里颜色相乘,但不包括 Alpha,Alpha后续单独计算 OUT.color = IN.color * _BaseColor * _RendererColor; return OUT; } half SampleSpriteAlpha(float2 uv) { // 若 UV 超出单张贴图边界(0~1),强制视作完全透明。 // 这解决了因图片边缘紧贴画布,受 Wrap Mode(Clamp/Repeat) 影响导致无法检测到外部透明度的问题 if (any(uv < 0.0) || any(uv > 1.0)) { return 0.0; } half a = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv).a; #if defined(ETC1_EXTERNAL_ALPHA) if (_EnableExternalAlpha > 0.0) { a = SAMPLE_TEXTURE2D(_AlphaTex, sampler_AlphaTex, uv).r; } #endif return a; } half3 SampleSpriteRGB(float2 uv) { half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv); return col.rgb; } half4 frag(Varyings IN) : SV_Target { // 1. 获取当前像素原始Alpha half originalAlpha = SampleSpriteAlpha(IN.uv); half baseAlphaMultiplier = IN.color.a; // 若原图毫无 Alpha,极其干净地剔除,提高性能 if (originalAlpha < 0.01) { return half4(0,0,0,0); } #if defined(_OUTLINE_ON) // --- 开启描边的情况 --- // 2. 多重采样寻找该像素周围一定范围内,Alpha的最小值 float2 offsetX = float2(_MainTex_TexelSize.x, 0.0) * _OutlineWidth; float2 offsetY = float2(0.0, _MainTex_TexelSize.y) * _OutlineWidth; float2 offsetDiag = float2(_MainTex_TexelSize.x, _MainTex_TexelSize.y) * _OutlineWidth * 0.7071; half minAlpha = originalAlpha; minAlpha = min(minAlpha, SampleSpriteAlpha(IN.uv + offsetX)); minAlpha = min(minAlpha, SampleSpriteAlpha(IN.uv - offsetX)); minAlpha = min(minAlpha, SampleSpriteAlpha(IN.uv + offsetY)); minAlpha = min(minAlpha, SampleSpriteAlpha(IN.uv - offsetY)); minAlpha = min(minAlpha, SampleSpriteAlpha(IN.uv + offsetDiag)); minAlpha = min(minAlpha, SampleSpriteAlpha(IN.uv - offsetDiag)); minAlpha = min(minAlpha, SampleSpriteAlpha(IN.uv + float2(-offsetDiag.x, offsetDiag.y))); minAlpha = min(minAlpha, SampleSpriteAlpha(IN.uv + float2(offsetDiag.x, -offsetDiag.y))); // 3. 计算“这是否是一个描边像素” (0:本体, 1:描边) half isEdge = 1.0 - smoothstep(_AlphaThreshold - _OutlineSmoothness, _AlphaThreshold + _OutlineSmoothness, minAlpha); // 4. 透明度混合 half finalObjectAlpha = originalAlpha * baseAlphaMultiplier * _BaseAlpha; half finalOutlineAlpha = originalAlpha * baseAlphaMultiplier * _OutlineColor.a; half finalAlpha = lerp(finalObjectAlpha, finalOutlineAlpha, isEdge); // 5. 计算颜色 half3 rawColor = SampleSpriteRGB(IN.uv) * IN.color.rgb; // 包含了BaseColor half3 finalRGB = lerp(rawColor, _OutlineColor.rgb, isEdge); return half4(finalRGB, finalAlpha); #else // --- 关闭描边的情况,以极低开销渲染原本材质 --- half finalObjectAlpha = originalAlpha * baseAlphaMultiplier * _BaseAlpha; half3 rawRGB = SampleSpriteRGB(IN.uv) * IN.color.rgb; return half4(rawRGB, finalObjectAlpha); #endif } ENDHLSL } } }