
Shader "Custom/SpriteOuterGlow"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
// --- 发光参数 ---
[HDR] _GlowColor ("Glow Color", Color) = (1,1,1,1) // 支持HDR,配合Bloom更亮
_OutlineWidth ("Outline Width", Range(0, 10)) = 1 // 发光宽度
_Threshold ("Alpha Threshold", Range(0, 1)) = 0.05 // 判定边缘的Alpha阈值
// --- 呼吸动画 ---
[Toggle] _EnablePulse ("Enable Pulse", Float) = 0
_PulseSpeed ("Pulse Speed", Float) = 2
}
SubShader
{
Tags
{
"Queue"="Transparent"
"IgnoreProjector"="True"
"RenderType"="Transparent"
"PreviewType"="Plane"
"CanUseSpriteAtlas"="True"
}
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha // 标准Premultiplied Alpha混合
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile _ PIXELSNAP_ON
#include "UnityCG.cginc"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
};
fixed4 _Color;
// 声明纹理的像素尺寸变量,Unity会自动填充
// xy = 1/width, 1/height
float4 _MainTex_TexelSize;
fixed4 _GlowColor;
float _OutlineWidth;
float _Threshold;
float _EnablePulse;
float _PulseSpeed;
v2f vert(appdata_t IN)
{
v2f OUT;
OUT.vertex = UnityObjectToClipPos(IN.vertex);
OUT.texcoord = IN.texcoord;
OUT.color = IN.color * _Color;
#ifdef PIXELSNAP_ON
OUT.vertex = UnityPixelSnap (OUT.vertex);
#endif
return OUT;
}
sampler2D _MainTex;
fixed4 frag(v2f IN) : SV_Target
{
// 1. 采样原始颜色
fixed4 c = tex2D(_MainTex, IN.texcoord) * IN.color;
c.rgb *= c.a; // 预乘Alpha处理
// 如果当前像素已经很实了,就不需要画外边框了(优化)
// 这里的 0.95 是防止内部也有半透明像素被描边
if (c.a > 0.95) return c;
// 2. 采样周围 4 个点 (上、下、左、右)
// 偏移量 = 像素单位 * 宽度系数
float2 offset = _MainTex_TexelSize.xy * _OutlineWidth;
fixed alphaSum = 0;
alphaSum += tex2D(_MainTex, IN.texcoord + float2(offset.x, 0)).a; // 右
alphaSum += tex2D(_MainTex, IN.texcoord - float2(offset.x, 0)).a; // 左
alphaSum += tex2D(_MainTex, IN.texcoord + float2(0, offset.y)).a; // 上
alphaSum += tex2D(_MainTex, IN.texcoord - float2(0, offset.y)).a; // 下
// --- 呼吸效果计算 ---
float pulse = 1.0;
if (_EnablePulse > 0.5)
{
// sin值域映射到 0.5 ~ 1.0 之间,避免完全黑掉
pulse = 0.5 + 0.5 * sin(_Time.y * _PulseSpeed);
}
// 3. 混合逻辑
// 如果周围有Alpha (alphaSum > 0) 且 当前是透明的,说明是边缘
// saturate 确保值在 0~1
float isGlow = saturate(alphaSum - c.a * 4);
// 硬切变(如果不想要渐变边缘,加上这句 step)
// isGlow = step(_Threshold, isGlow);
fixed4 glow = _GlowColor * isGlow * pulse;
// 叠加:原始颜色 + 发光颜色
// 记得处理 Alpha:如果这里是发光区,Alpha也要设为1,否则会被混合掉
c.rgb += glow.rgb;
c.a = max(c.a, isGlow * _GlowColor.a);
return c;
}
ENDCG
}
}
}