Shader "AIBIS/KiteFocusSprite" { Properties { [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} _Color ("Tint", Color) = (1, 1, 1, 1) [Header(Focus Blur)] _BlurAmount ("Blur Amount", Range(0, 1)) = 0 _BlurSize ("Blur Size", Range(0, 0.05)) = 0.012 [HideInInspector] _UseSoftFocus ("Use Soft Focus", Float) = 0 _DefocusBloom ("Defocus Highlight Bloom", Range(0, 1)) = 0.32 _HaloStrength ("Large Circle Halo", Range(0, 0.6)) = 0.22 _ChromaticFringe ("Lens Color Fringe", Range(0, 0.6)) = 0.14 _DefocusVeil ("Defocus White Veil", Range(0, 0.3)) = 0.055 // Required for SpriteMask (same contract as Sprites-Default / UI.Mask) [HideInInspector][PerRendererData] _StencilComp ("Stencil Comparison", Float) = 8 [HideInInspector][PerRendererData] _Stencil ("Stencil ID", Float) = 0 [HideInInspector][PerRendererData] _StencilOp ("Stencil Operation", Float) = 0 [HideInInspector][PerRendererData] _StencilWriteMask ("Stencil Write Mask", Float) = 255 [HideInInspector][PerRendererData] _StencilReadMask ("Stencil Read Mask", Float) = 255 [HideInInspector] _ColorMask ("Color Mask", Float) = 15 } SubShader { Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" "CanUseSpriteAtlas" = "True" } Cull Off Lighting Off ZWrite Off Blend One OneMinusSrcAlpha ColorMask [_ColorMask] Stencil { Ref [_Stencil] Comp [_StencilComp] Pass [_StencilOp] ReadMask [_StencilReadMask] WriteMask [_StencilWriteMask] } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float4 color : COLOR; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; fixed4 color : COLOR; float2 uv : TEXCOORD0; }; sampler2D _MainTex; float4 _MainTex_TexelSize; fixed4 _Color; float _BlurAmount; float _BlurSize; float _UseSoftFocus; float _DefocusBloom; float _HaloStrength; float _ChromaticFringe; float _DefocusVeil; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.color = v.color * _Color; o.uv = v.uv; return o; } half4 sampleTex(float2 uv) { // 边界外按透明,避免 saturate 夹边造成硬切 if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return half4(0, 0, 0, 0); return tex2D(_MainTex, uv); } half4 samplePremultiplied(float2 uv) { half4 color = sampleTex(uv); color.rgb *= color.a; return color; } // 圆形光圈核:比高斯更接近真实镜头的弥散圆,且不会出现十字形拖影。 half4 ApertureDisc(float2 uv, float radius) { half4 result = samplePremultiplied(uv) * 0.12; const half w = 0.044; // 内圈 result += samplePremultiplied(uv + float2( 0.38, 0.00) * radius) * w; result += samplePremultiplied(uv + float2(-0.38, 0.00) * radius) * w; result += samplePremultiplied(uv + float2( 0.00, 0.38) * radius) * w; result += samplePremultiplied(uv + float2( 0.00, -0.38) * radius) * w; result += samplePremultiplied(uv + float2( 0.27, 0.27) * radius) * w; result += samplePremultiplied(uv + float2(-0.27, 0.27) * radius) * w; result += samplePremultiplied(uv + float2( 0.27, -0.27) * radius) * w; result += samplePremultiplied(uv + float2(-0.27, -0.27) * radius) * w; // 外圈使用不规则角度,避免规则采样形成振铃或星芒。 result += samplePremultiplied(uv + float2( 0.98, 0.05) * radius) * w; result += samplePremultiplied(uv + float2( 0.82, 0.55) * radius) * w; result += samplePremultiplied(uv + float2( 0.43, 0.90) * radius) * w; result += samplePremultiplied(uv + float2(-0.16, 0.98) * radius) * w; result += samplePremultiplied(uv + float2(-0.69, 0.72) * radius) * w; result += samplePremultiplied(uv + float2(-0.99, 0.10) * radius) * w; result += samplePremultiplied(uv + float2(-0.80, -0.58) * radius) * w; result += samplePremultiplied(uv + float2(-0.36, -0.92) * radius) * w; result += samplePremultiplied(uv + float2( 0.22, -0.97) * radius) * w; result += samplePremultiplied(uv + float2( 0.72, -0.68) * radius) * w; result += samplePremultiplied(uv + float2( 0.94, -0.31) * radius) * w; result += samplePremultiplied(uv + float2(-0.48, 0.86) * radius) * w; return result; } half4 WideHalo(float2 uv, float radius) { half4 result = 0; result += samplePremultiplied(uv + float2( 1.00, 0.00) * radius); result += samplePremultiplied(uv + float2( 0.71, 0.71) * radius); result += samplePremultiplied(uv + float2( 0.00, 1.00) * radius); result += samplePremultiplied(uv + float2(-0.71, 0.71) * radius); result += samplePremultiplied(uv + float2(-1.00, 0.00) * radius); result += samplePremultiplied(uv + float2(-0.71, -0.71) * radius); result += samplePremultiplied(uv + float2( 0.00, -1.00) * radius); result += samplePremultiplied(uv + float2( 0.71, -0.71) * radius); return result * 0.125; } half4 SoftFocusBlur(float2 uv, float radius) { float nearRadius = radius * 0.48; half4 result = samplePremultiplied(uv) * 0.20; result += samplePremultiplied(uv + float2( nearRadius, 0)) * 0.08; result += samplePremultiplied(uv + float2(-nearRadius, 0)) * 0.08; result += samplePremultiplied(uv + float2(0, nearRadius)) * 0.08; result += samplePremultiplied(uv + float2(0, -nearRadius)) * 0.08; result += samplePremultiplied(uv + float2( nearRadius, nearRadius)) * 0.055; result += samplePremultiplied(uv + float2(-nearRadius, nearRadius)) * 0.055; result += samplePremultiplied(uv + float2( nearRadius, -nearRadius)) * 0.055; result += samplePremultiplied(uv + float2(-nearRadius, -nearRadius)) * 0.055; result += samplePremultiplied(uv + float2( radius, 0)) * 0.04; result += samplePremultiplied(uv + float2(-radius, 0)) * 0.04; result += samplePremultiplied(uv + float2(0, radius)) * 0.04; result += samplePremultiplied(uv + float2(0, -radius)) * 0.04; result += samplePremultiplied(uv + float2( radius, radius)) * 0.025; result += samplePremultiplied(uv + float2(-radius, radius)) * 0.025; result += samplePremultiplied(uv + float2( radius, -radius)) * 0.025; result += samplePremultiplied(uv + float2(-radius, -radius)) * 0.025; return result; } fixed4 frag(v2f i) : SV_Target { float blurAmt = saturate(_BlurAmount); half4 sharp = sampleTex(i.uv); if (blurAmt < 0.001) { half4 col = sharp * i.color; col.rgb *= col.a; return col; } float2 texel = max(abs(_MainTex_TexelSize.xy), float2(1e-5, 1e-5)); float radius = (length(texel) * 0.5 + _BlurSize) * (0.28 + blurAmt * 1.72); if (_UseSoftFocus > 0.5) { half4 softBlurred = SoftFocusBlur(i.uv, radius); half4 softSharp = sharp; softSharp.rgb *= softSharp.a; half4 softColor = lerp(softSharp, softBlurred, smoothstep(0.0, 1.0, blurAmt)); softColor.rgb *= i.color.rgb * i.color.a; softColor.a *= i.color.a; return softColor; } half4 disc = ApertureDisc(i.uv, radius); half4 halo = WideHalo(i.uv, radius * 1.85); half4 blurred = lerp(disc, halo, saturate(_HaloStrength * blurAmt)); // 很轻的 RGB 焦平面差异,避免单纯软件模糊的塑料感。 float2 fringe = float2(0.73, 0.41) * radius * _ChromaticFringe * blurAmt; half4 fringeR = samplePremultiplied(i.uv + fringe); half4 fringeB = samplePremultiplied(i.uv - fringe); blurred.r = lerp(blurred.r, fringeR.r, _ChromaticFringe * blurAmt); blurred.b = lerp(blurred.b, fringeB.b, _ChromaticFringe * blurAmt); // 参考片中的失焦会让亮部扩散并稍微泛白,但保留在物体 alpha 内。 half luminance = dot(blurred.rgb, half3(0.2126, 0.7152, 0.0722)); half highlight = saturate(luminance - blurred.a * 0.42); blurred.rgb += highlight * _DefocusBloom * blurAmt; blurred.rgb += blurred.a * _DefocusVeil * blurAmt; half4 sharpPremul = sharp; sharpPremul.rgb *= sharpPremul.a; float mixT = smoothstep(0.0, 1.0, blurAmt); half4 col = lerp(sharpPremul, blurred, mixT); col.rgb *= i.color.rgb * i.color.a; col.a *= i.color.a; return col; } ENDCG } } }