01 · 什么是 UI 流光效果?
在游戏 UI 中,**流光(Shimmer / Sweep Light)**是一道明亮的高光带,从控件的一侧扫向另一侧,营造出金属质感或魔法光泽。常见于:
- 按钮 Hover / 点击反馈
- 卡片解锁过渡动画
- 血条、进度条高光
- 金币、装备品质光泽

本质上,它只是一道 随时间横向移动的半透明白色高光带叠在原始纹理上方。看起来复杂,数学其实只有几行。
2.1 UV 坐标是什么?
每个 UI 顶点都带有一组 UV 坐标 (u, v) ,范围 [0, 1]。u 对应横向(左→右),v 对应纵向(下→上)。

2.2 光带 = UV 中一段横向区间
我们只需要在 某个 u 值附近,让像素变亮------这就是一条竖直光带。让这个 u 值随时间增大,光带就"向右扫"了。
核心思路: 当像素的 u 在 [center - half_width, center + half_width] 区间内时,混入高光颜色。center 随 _Time 从 −0.2 移动到 1.2,就产生了扫光效果。
03 · 数学推导:构造光带函数
3.1 方案一:阶梯函数(硬边)
最简单的判断:像素的 u 是否在光带范围内?
light = (abs(u − center) < halfWidth) ? 1.0 : 0.0
问题:边缘是硬切的,看起来不自然,如下图左所示。

3.2 方案二:smoothstep 柔边
HLSL 内置函数 smoothstep(a, b, x):
- 当
x < a时返回0 - 当
x > b时返回1 - 中间用三次 Hermite 曲线平滑过渡:
t² × (3 - 2t)
// 构造以 center 为中心、宽度为 2×half 的柔边光带
float d = abs(u - center);
float light = 1.0 - smoothstep(half*0.3, half, d);
d 是像素到光带中心的距离。smoothstep 让靠近中心的像素亮度为 1,向边缘渐变到 0,产生自然的高斯状衰减感。
3.3 方案三:用 Gaussian 模拟(更真实)
更进一步,可以用高斯曲线模拟光晕:
float d = u - center;
float light = exp(-d*d / (2.0 * sigma*sigma));
但 exp() 在移动端稍贵,实际上 smoothstep 方案已经够用。
3.4 添加时间:让光带动起来
Unity 在 Shader 中提供了内置变量 _Time,其中 _Time.y 是以秒为单位的游戏运行时间。
// 让 center 从 -0.2 扫到 1.2(超出边界保证进出都顺滑)
float speed = _Speed;
float center = frac(_Time.y * speed) * 1.4 - 0.2;
frac(x) 取小数部分,使 center 在 [-0.2, 1.2] 之间循环。

04 · 逐步实现 Shader 代码
4.1 基础版(10 行核心代码)
cs
// UIShimmer_Basic.shader
Shader "UI/Shimmer_Basic"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Speed ("Sweep Speed", Float) = 0.8
_Width ("Band Width", Float) = 0.15
_Brightness("Brightness", Float) = 1.5
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent"
"IgnoreProjector"="True" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float _Speed, _Width, _Brightness;
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.color = v.color;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// ① 采样原始贴图颜色
fixed4 col = tex2D(_MainTex, i.uv) * i.color;
// ② 计算光带中心位置(循环 -0.2 → 1.2)
float center = frac(_Time.y * _Speed) * 1.4 - 0.2;
// ③ 计算当前像素到光带中心的距离
float d = abs(i.uv.x - center);
// ④ smoothstep 生成柔边强度
float shimmer = 1.0 - smoothstep(_Width * 0.3, _Width, d);
// ⑤ 将高光叠加到原始颜色
col.rgb += shimmer * _Brightness * col.a;
return col;
}
ENDCG
}
}
}
第 ⑤ 步解读: 用 col.a 乘以 shimmer,保证透明区域不会被高光影响------这样即使是圆角按钮,流光也只出现在不透明区域内。

05 · 进阶:倾斜 + 柔边 + 颜色混合
5.1 让光带倾斜
真实的扫光通常是 斜的(约 30°~45°)。只需在计算距离时,把 v 方向混入 u:
// 倾斜:把 v 分量按比例加入 u,模拟旋转
float u_skew = i.uv.x + i.uv.y * _Skew;
float d = abs(u_skew - center);
当 _Skew = 0 时光带垂直,_Skew = 0.5 时约 27°,_Skew = 1.0 时约 45°。

5.2 光带颜色定制
将白色高光换成可配置的颜色:
cs
// Properties 中添加:
_ShimmerColor ("Shimmer Color", Color) = (1,1,1,1)
// Fragment Shader 中替换叠加:
fixed3 highlight = shimmer * _ShimmerColor.rgb * _Brightness;
col.rgb = lerp(col.rgb, col.rgb + highlight, col.a);
cs
Shader "UI/Shimmer_Advanced"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Speed ("Sweep Speed", Float) = 0.8
_Width ("Band Width", Float) = 0.15
_Brightness ("Brightness", Float) = 1.5
_Skew ("Skew", Float) = 0.5
_ShimmerColor ("Shimmer Color",Color) = (1,1,1,1)
_Interval ("Pause Interval",Float) = 2.0
}
SubShader
{
Tags { "Queue"="Transparent" "RenderType"="Transparent"
"IgnoreProjector"="True" "PreviewType"="Plane" }
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float _Speed, _Width, _Brightness, _Skew, _Interval;
fixed4 _ShimmerColor;
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
struct v2f {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.color = v.color;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv) * i.color;
// ── 带间隔的循环时间 ──────────────────────
// totalCycle = 扫一次的时间 + 暂停时间
float cycleDur = 1.0 / _Speed + _Interval;
float t = fmod(_Time.y, cycleDur);
// 只在前 1/_Speed 秒内扫光,其余时间暂停
float progress = saturate(t * _Speed);
float center = progress * 1.4 - 0.2;
// ── 倾斜 UV ────────────────────────────────
float u_skew = i.uv.x + i.uv.y * _Skew;
// ── 光带强度 ───────────────────────────────
float d = abs(u_skew - center);
float shimmer = 1.0 - smoothstep(_Width * 0.3, _Width, d);
// ── 颜色叠加(仅不透明处) ─────────────────
fixed3 light = shimmer * _ShimmerColor.rgb * _Brightness;
col.rgb = lerp(col.rgb, col.rgb + light, col.a);
return col;
}
ENDCG
}
}
}

关键技巧: fmod(_Time.y, cycleDur) 配合 saturate(t * _Speed),实现"扫一次→停顿→再扫"的间歇效果,比一直循环更有节奏感,也更省眼睛。
06 · 在 Unity 中使用
-
创建 Shader 文件
在 Project 面板右键 →
Create → Shader → Unlit Shader,将内容替换为上面的进阶版代码,重命名为UIShimmer_Advanced。 -
创建 Material
右键 →
Create → Material,在 Inspector 中将 Shader 设置为UI/Shimmer_Advanced。 -
挂到 Image 组件
选中 Button 或 Image 节点,将
Image.Material字段拖入新 Material。Unity UI 会自动把Sprite传给_MainTex。 -
调节参数
在 Material Inspector 中实时调节 Speed / Width / Brightness / Skew,效果立刻可见。
-
(可选)C# 动态控制开关
通过脚本控制材质参数,如 Hover 时才启动流光:
cs
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class ShimmerButton : MonoBehaviour,
IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] private Image _image;
private Material _mat;
void Start()
{
// 克隆一份,避免修改共享 Material
_mat = Instantiate(_image.material);
_image.material = _mat;
// 初始速度设为 0(不运动)
_mat.SetFloat("_Speed", 0f);
}
public void OnPointerEnter(PointerEventData _)
=> _mat.SetFloat("_Speed", 1.2f);
public void OnPointerExit(PointerEventData _)
=> _mat.SetFloat("_Speed", 0f);
}
注意: 直接修改 image.material 会修改所有使用该 Material 的对象!一定要先 Instantiate() 克隆一份,或使用 image.materialForRendering(只读)配合 MaterialPropertyBlock。
07 · 参数调节参考
| 参数 | 推荐值 | 效果说明 |
|---|---|---|
_Speed |
0.5 ~ 1.5 | 越大扫得越快;0 = 静止 |
_Width |
0.1 ~ 0.25 | 光带宽度;太大会覆盖整个控件 |
_Brightness |
0.8 ~ 2.5 | 亮度增益;移动端建议 ≤ 1.5 |
_Skew |
0.3 ~ 0.8 | 倾斜量;0 垂直,1 约 45° |
_Interval |
1.5 ~ 4.0 | 两次扫光之间的暂停秒数 |
_ShimmerColor |
#FFFFFF / 金色 | 高光颜色;金色 (1, 0.85, 0.5) 有金属感 |
总结
整个流光效果的核心只有 5 行数学:
- 用
frac(_Time.y * _Speed)生成 0→1 的循环进度 - 把进度映射到
[-0.2, 1.2]得到光带中心center - 计算像素 UV 到 center 的距离
d(可加倾斜) - 用
smoothstep把距离转换成柔边亮度shimmer - 用
lerp/ 加法把高光混入原始颜色
剩下的都是外壳和工程细节。掌握这个思路,你可以把它扩展到环形进度条流光、血条扫光、地图边界发光等各种 UI 场景。