☰
Unity Shader实现Logo流光效果:从UV坐标到透明遮罩
2026/10/9 3:22:28 网站建设 项目流程

简介:这是一份面向Unity开发者与游戏视觉初学者的Shader流光效果工程包,解决Logo、UI图标或游戏标题等场景的动态扫光与流动高光需求。资源共43个文件,包含两套shader、两套mat材质、20个asset资源、png贴图及Unity项目设置文件,压缩包仅92KB,可直接导入Unity查看完整结构。已有1423人学习下载。工程覆盖纯代码ShaderLab与流光贴图滑动两种方案:前者通过时间变量、颜色插值与混合生成流动光线,后者通过UV动画让贴图在Logo表面循环扫过;同时附带Shader Graph节点思路及工程配置,方便对照材质参数、Shader属性和场景组织方式快速上手。工程场景目录清晰,材质与Shader分离,适合二次修改和教学演示。开发者可借此理解表面着色器、顶点/片段着色器、纹理Wrap Mode循环等关键知识点,也可以直接修改流光速度、颜色与透明度,制作更个性化的Logo效果。

1. Logo流光效果是什么:一段Shader让品牌图动起来

接到启动屏需求:Logo要带一圈流光扫过,客户说要有质感、要高级、还要一遍遍循环。不上AE序列帧,也不想为单个UI动效挂粒子系统。这叫 Logo 流光效果,本质上就是在采样Logo贴图时,叠一条随时间移动的高亮光带。它在 Unity Shader 里被拆成四件事:UV怎么取、时间怎么进、光带怎么算、透明区怎么遮。适合正在做启动屏、加载页、活动海报和图标动效的Unity开发者,也适合被序列帧体积拖累包体、被粒子回收问题反复折磨过的人换一条路。

2. 先把UV坐标系玩明白:流光效果的最小Shader实现

2.1 流光效果的本质:一道随时间移动的采样光

先把概念盘清楚。Shader 里每个像素拿到的 uv 是一个 0 到 1 的坐标,uv.x 是横坐标,uv.y 是纵坐标,不管贴图本身是多少分辨率,这张图都被映射到一个正方形里。所谓流光,就是利用这个坐标算出一个“亮带的位置”,让这个位置随时间移动。时间变量 _Time.y 在 Unity 里是从场景加载开始计的秒数,把它乘一个速度系数,再和 uv.x 做差,光带就动起来了;外面套一个 frac() 让位置循环回起点,动画就能无限循环。

把“位置”和“时间”都放在 GPU 上算,这个效果根本不需要 CPU 参与。很多 Unity 进阶书籍讲 UV 时都在强调采样、混合、翻转,真正把它当作一个可计算的坐标系来用的例子反而少,流光效果恰恰是最典型的场景。光带本身也不复杂:在某个 x 位置附近让亮度从 0 提到 1 再降回 0,就是一个亮带。实现上最稳妥的是 smoothstep,它会在两个边界之间做平滑过渡,返回 0 到 1 的连续值,比用 if 判断光带范围干净得多,也避免了肉眼可见的硬边。

2.2 能直接复制的最小Shader:让Logo出现一条扫过的光带

下面这段是我保留的最小版本,删掉了所有非必要分支,直接交给 Unity 的 Built-in 渲染管线用。新建一个 Shader 文件,粘贴进去,建材质赋给 UI 的 Image 或者场景里的 Quad 就能看到效果。

Shader "Custom/LogoShineSimple" { Properties { _MainTex ("Logo Texture", 2D) = "white" {} _ShineColor ("Shine Color", Color) = (1, 1, 1, 1) _Speed ("Scroll Speed", Range(0.0, 10.0)) = 2.0 _Width ("Band Width", Range(0.05, 0.5)) = 0.2 } SubShader { Tags { "Queue"="Transparent" "RenderType"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha Cull Off ZWrite Off Lighting Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _ShineColor; float _Speed; float _Width; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); // 光带位置:uv.x 沿负方向移动,速度由 _Time.y * _Speed 控制 float pos = frac(i.uv.x - _Time.y * _Speed); // 光带中心假设在 0.5,距离中心越远亮度越低 float dist = abs(pos - 0.5); float band = 1.0 - smoothstep(0.0, _Width, dist); // 把光带颜色叠加到原图颜色上,保留原图 alpha float3 finalColor = col.rgb + _ShineColor.rgb * band; return fixed4(finalColor, col.a); } ENDCG } } }

这段代码的核心是 frag 里的三行。frac(i.uv.x - _Time.y * _Speed)让每个像素拿到的横坐标随时间均匀后移,frac 保证结果始终落在 0 到 1 之间,因此动画天然循环。abs(pos - 0.5)算的是当前像素离光带中心的距离,距离为 0 时最亮。smoothstep(0.0, _Width, dist)在 dist 小于 _Width 的范围内做平滑渐变,外面用 1 减,得到中间亮、两侧暗的带状分布。把 band 乘以 _ShineColor 再加到原图上,就是一道白光扫过的效果。

创建步骤很简单:Project 窗口右键 Create -> Shader -> Unlit Shader,把上面整段覆盖进去;再右键 Create -> Material,把 Shader 改成 Custom/LogoShineSimple;把带透明通道的 Logo 图拖给 _MainTex,最后把材质拖到目标物体上。如果看不见效果,先确认贴图的 alpha 不是全黑的,再确认材质的 Render Queue 在 Transparent 区间。

2.3 参数调试第一课:_Width 和 _Speed 的观感边界

这两个参数直接决定效果是“高级”还是“翻车”。_Speed 在 0.5 左右时,光带移动缓慢,适合承载 Logo 的正式亮相;1.0 到 2.0 是常见运营海报扫光的节奏,肉眼能清楚看到一道光扫过;超过 5.0 基本就变成高频闪烁,只适合特殊氛围。_Width 是光带半宽,0.2 左右是一条饱满但不笨重的亮带,0.05 就只剩一条细线,0.4 以上会把 Logo 盖掉一大块,看起来像曝光过度。

我通常把 _Width 和 _Speed 一起调:先固定速度,把宽度调到能覆盖 Logo 主体面积的五分之一到四分之一,再回头调速度。很多人第一步就把 _Width 拉到 0.4 以上,结果整个 Logo 常亮,流光效果完全被埋没。> 注意:_Speed 尽量不要设为整数,比如 1.0 或 2.0。整数倍速度配合某些贴图尺寸时,光带回到起点的那一帧会和起始位置完全重合,产生一帧明显停顿;0.7、1.3 这种非整数值会让循环错开,观感更顺。

3. 从单光带到完整流光:角度旋转、多光带与颜色分层

3.1 绕中心旋转UV:让光带斜着扫

水平扫光看腻了以后,第一个想改的就是角度。要让光带斜着走,最简单的做法是把 UV 绕某个中心旋转一个角度,再沿旋转后的横轴算光带。旋转矩阵在 Shader 里写起来很朴素,关键是先减 0.5 把坐标原点挪到图像中心,否则绕的是左下角,旋转出来的光带位置会偏得离谱。旋转后再加回 0.5 也可以,但对流光来说反正都是采样一个坐标,不加也行。

// 先把 UV 中心移到 (0,0),这样旋转是绕图像中心转 float2 uv = i.uv - 0.5; // 用角度 _Angle 计算正弦余弦,sincos 一次调用比 sin(angle) + cos(angle) 更省 float s, c; sincos(_Angle, s, c); // 绕原点旋转:x' = x*cos - y*sin, y' = x*sin + y*cos float2 rotated = float2(uv.x * c - uv.y * s, uv.x * s + uv.y * c); // 后续就把 rotated.x 当作原来的 i.uv.x 来用 float pos = frac(rotated.x - _Time.y * _Speed);

_Angle单位是弧度,不是角度。想从 Inspector 里直观地填 45 度,要么填 0.785(也就是 PI/4),要么在属性里加一句_Angle ("Angle", Range(-3.14, 3.14)) = 0.5,并接受它显示为小数。这个旋转矩阵本身也可以用在很多其他 2D Shader 效果里,不只是流光。旋转之后的点如果超出 0 到 1 范围,frac 依然会把它拉回来,所以不用担心越界。

3.2 Tiling叠加与多光带:流光从一道变成一组

单条光带扫多了容易显得单薄,尤其是大尺寸 Logo。常见做法是让多道光带依次扫过,或者让几条光带保持固定间隔一起移动。实现方式是在 rotated.x 上乘一个 _Tiling,相当于把坐标轴压缩,这样原来一个单位里的一条光带,现在变成了多条。用 frac 取小数后,每个单元里都有一条相同的光带,视觉上就是一组等距流光。

Shader "Custom/LogoShineAdvanced" { Properties { _MainTex ("Logo Texture", 2D) = "white" {} _ShineColor ("Shine Color", Color) = (1, 1, 1, 1) _Speed ("Scroll Speed", Range(0.0, 10.0)) = 0.8 _Width ("Band Width", Range(0.05, 0.3)) = 0.1 _Angle ("Band Angle (Radian)", Range(-3.14, 3.14)) = 0.4 _Tiling ("Band Tiling", Range(1.0, 6.0)) = 2.0 _Intensity ("Shine Intensity", Range(0.0, 3.0)) = 1.2 } SubShader { Tags { "Queue"="Transparent" "RenderType"="Transparent" } Blend SrcAlpha OneMinusSrcAlpha Cull Off ZWrite Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; float4 _MainTex_ST; fixed4 _ShineColor; float _Speed; float _Width; float _Angle; float _Tiling; float _Intensity; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); return o; } fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); // 中心化并旋转,让光带方向由 _Angle 控制 float2 uv = i.uv - 0.5; float s, c; sincos(_Angle, s, c); float2 rotated = float2(uv.x * c - uv.y * s, uv.x * s + uv.y * c); // Tiling 决定一个循环里有几条光带 float x = rotated.x * _Tiling - _Time.y * _Speed; float pos = frac(x); // 距离光带中心越远越暗,再用平方把光带收得更聚拢 float dist = abs(pos - 0.5); float band = 1.0 - smoothstep(0.0, _Width, dist); band = band * band; // 混合最终颜色,_Intensity 控制整体亮度 float3 finalColor = col.rgb + _ShineColor.rgb * band * _Intensity; return fixed4(finalColor, col.a); } ENDCG } } }

_Tiling 从 1.0 改到 2.0,光带数量立增,但要注意 _Width 要相应调小,否则相邻光带会连在一起变成大面积泛光。band 平方这一步是我非常喜欢用的收敛手段:它让光带中心更亮、边缘衰减更快,视觉上从“一块布”变成“一道光”。参数参考如下。

参数常用范围说明
_Speed0.5 ~ 2.0光带移动节奏,越大越快
_Width0.05 ~ 0.15配合 _Tiling 使用,光带越密宽度越小
_Angle-1.57 ~ 1.57对应 -90 度到 90 度的斜向扫光
_Tiling1.0 ~ 3.0超过 3 条容易显乱,适合小 Logo
_Intensity0.8 ~ 1.5超过 1.5 高光区会过曝

3.3 颜色三层混合:主色、光带色与亮度收敛

把 _ShineColor 设成纯白最简单,但真正耐看的流光通常不是纯白,而是带一点品牌色的高亮。常见做法是选一个比 Logo 主色亮两三档的颜色,或者直接取 Logo 主色然后提亮饱和度。混合公式也值得斟酌:直接相加会让光带覆盖的区域发白,_Intensity 一大就容易过曝;用 lerp 则可以保留更多原图色相。

// 方案一:直接加,适合暗色底和简洁 Logo float3 finalColor = col.rgb + _ShineColor.rgb * band * _Intensity; // 方案二:lerp,保留原图颜色,适合彩色 Logo float3 finalColor = lerp(col.rgb, col.rgb + _ShineColor.rgb, band * _Intensity);

两种方案差在“光带对原图颜色的替换程度”。方案二当 band 接近 1 时,光带颜色占主导,Logo 原本的细节会变淡;方案一始终在原始颜色上加亮,暗部细节保留更完整。我一般给带金属质感的 Logo 用方案一,给色彩丰富的品牌吉祥物用方案二。没有绝对对错,用起来看颜色断层是否明显。

4. 让流光只在Logo内部流动:Alpha遮罩与混合模式

4.1 透明区光块问题:为什么流光会扫出Logo边界

头一次把这段 Shader 挂到透明 PNG 上时,大概率会撞见一个让人皱眉的现象:光带扫过时,Logo 之外的透明区域也亮了一块,像一个白色矩形在画面上来回飘。原因很直接——frag 里算出的 band 覆盖了整张贴图,而透明区域的 alpha 是 0,Shader 却还是把光带颜色加到了 rgb 上,于是透明的像素被硬生生填充成了光带颜色。

排查优先级最高的点:先看贴图有没有真正的 alpha 通道。很多美工导出的 PNG 是 RGB 加一个全白的通道,看着透明,实际上 alpha 全是 255,这种情况怎么遮都没用。确认贴图的导入设置里 Alpha Is Transparency 勾上了,且 Sprite 模式下 alpha 正确,再回头看 Shader。

4.2 用clip做Alpha遮罩:丢弃透明像素的正确姿势

遮罩最直接的手段是 clip。在 tex2D 之后调用 clip(col.a - 0.5),alpha 小于 0.5 的片段会在光栅化阶段被直接丢弃,不会进入颜色混合,光带自然就只能在 Logo 有形状的地方出现。注意 clip 不只是绕过光带,它把整个像素都丢了,如果原图边缘有半透明过渡,这些过渡也会被一刀切掉。

fixed4 frag (v2f i) : SV_Target { fixed4 col = tex2D(_MainTex, i.uv); // 丢弃 alpha 低于阈值的像素,防止光带出现在透明区 clip(col.a - 0.5); // 后面的流光计算保持不变 float pos = frac(i.uv.x - _Time.y * _Speed); float dist = abs(pos - 0.5); float band = 1.0 - smoothstep(0.0, _Width, dist); band = band * band; float3 finalColor = col.rgb + _ShineColor.rgb * band * _Intensity; return fixed4(finalColor, col.a); }

如果 Logo 本身带柔边,clip 之后边缘会变硬,甚至出现一圈细碎锯齿。这种场景我更推荐用软遮罩:不丢弃像素,而是把光带亮度乘以 alpha,让透明区域的光带自然衰减。

// 软遮罩:光带亮度随 alpha 同步衰减,透明区不会发亮 float maskedBand = band * col.a; float3 finalColor = col.rgb + _ShineColor.rgb * maskedBand * _Intensity; return fixed4(finalColor, col.a);

软遮罩的代价是透明区依然会参与渲染,只是颜色没变化,所以在 alpha 为 0 的像素上会有少量无效的算力开销。对 UI 里的 Logo 完全可以接受。> 提示:剪影型 Logo 用 clip 干净利落;带柔和投影、渐变边缘的 Logo 用软遮罩更自然。两套代码可以做成一个开关,用 Shader 的 Property 控制走哪条分支。

4.3 混合模式与渲染顺序:Transparent队列的配置细节

SubShader 开头的 Tags 和 Blend 决定了这张 Logo 在渲染管线里的位置。Queue=Transparent 让物体排到不透明物体之后、UI 特殊层里;ZWrite Off 防止透明物体写入深度缓冲,否则后面再画的透明物体会被挡掉;Blend SrcAlpha OneMinusSrcAlpha 是标准的半透明混合,光带亮度和 Logo 原先的透明度正确叠加。

配置项值作用
QueueTransparent纳入透明渲染队列
RenderTypeTransparent配合透明材质分组
ZWriteOff不写深度,避免遮挡排序问题
BlendSrcAlpha OneMinusSrcAlpha标准 alpha 混合
CullOff双面渲染,兼容 UI 面片和翻转的模型

还有一个容易忽略的细节:如果这个材质是给 Canvas 下 UI 的 Image 用的,Unity UI 默认会传顶点色给 Shader,用来实现 Image 组件上的 Color 调色。上面这几个版本没写 COLOR 语义,体现在编辑器里就是 Image 的 Color 属性改了没反应,但效果本身正常。要兼容 UI 调色,需要在 v2f 里加一个float4 color : COLOR,并在 frag 最后乘上它。

struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 color : COLOR; }; // 片段着色器里返回时乘上顶点色 return fixed4(finalColor * i.color.rgb, col.a * i.color.a);

这样改完之后,Shader 就能同时服务 UI 和 3D 场景,同一个材质来回拖都能用。

5. 流光效果避坑指南:五个让人半夜改代码的常见问题

5.1 材质变紫红色并报d3d11错误

现象:材质球变成紫红色,Console 里报 a d3d11-compatible gpu (feature level 11.0, shader model 5.0) is required to run this shader,编辑器里实时渲染也看不到效果。原因通常是 Shader 里用了 DX11 或 Shader Model 5.0 专属语法,比如 uint、StructuredBuffer、SV_Coverage,或者 #pragma target 被设成了 5.0,而当前显卡或 API 只支持到 3.0。网上拷下来的高阶 Shader 最容易踩这个。解决:搜一遍代码里有没有 #pragma target,降到 3.0;把 uint 改成 float,把 StructuredBuffer 改成纹理采样;如果代码来自 Shader Forge 或旧版 Shader Graph,重建一遍节点输出往往比手工改更省事。

5.2 光带扫出了Logo边界

现象:光带确实在动,但 Logo 四周的透明区域也被照亮,甚至整体像贴了一个会移动的发光贴图。原因:alpha 从未参与光带计算,透明区域的 r、g、b 被强行加了颜色。如果贴图本身没有 alpha 通道,那就不是 Shader 的问题是资源问题。解决:按第 4 章的方式先 clip 或乘 alpha。我见过最快的解决路径是先把 clip(col.a - 0.5) 加上,看一眼效果;如果边缘被切坏再换软遮罩。两种办法都能在一分钟内验证完判断。

5.3 边缘锯齿与低分辨率马赛克

现象:光带斜着扫时边缘有阶梯状锯齿,在 101 或者低分辨率手机上特别明显。原因:半透明混合下的高对比度边缘天生容易出锯齿;smoothstep 的过渡区间如果只有几像素,放大后就是一个陡峭断崖。解决:把 _Width 稍微调大一点,让光带边缘的衰减范围更宽;band 平方之后边缘会更钝;如果项目开了 MSAA,确认 Canvas 的材质不被强制关掉抗锯齿;更彻底的办法是把 Logo 贴图本身的边缘留出 2 到 4 像素的渐变 alpha,而不是让美工做成 0/1 硬边。

5.4 光带回卷跳变:循环节奏的处理

现象:光带扫完一遍回到起点时,会突然闪一下或亮一帧,停顿感极强。原因:frac 在整数位置直接跳回 0,如果光带在跳变瞬间还处于可见区域,这一帧就会形成一个瞬移。解决:让光带在离开画面后才回卷。最简单的手段是调大 _Tiling 或者缩小 _Width,保证光带完整扫出 Logo 边缘后,位置才触及循环边界。另一种做法是放弃 frac,改用正弦做往复扫动,让光带来回走而不是单向前进,虽然运动表现变了,但彻底消灭了跳变。两种方案我都在项目里用过,优先检查宽度和循环周期的比例,不行再换运动函数。

5.5 透明排序与Draw Call:UI里Shader的隐藏成本

现象:Logo 流光在场景里正常,放进 UI 后被按钮挡住,或者把按钮盖住;同时 Profiler 里一个 Logo 多出好几个批次。原因:透明物体没有深度写入,Canvas 自身按层级和材质实例排序;每个 Logo 单独建材质实例时,Canvas 会把它拆成独立的 Draw Call,一个页面三五个 Logo 就是三五个批次。解决:同一批 Logo 共用一个材质实例,只替换贴图,减少材质数量;把 Canvas 的 Sorting Order 设置到正确层级,让 Logo 和按钮的位置关系符合预期。真机 Profile 时重点看 Render 环节的批次涨幅,如果从 5 涨到 10,多半是材质实例散掉了。这也是 Unity 游戏优化里最容易被忽略的一个点:不是 Shader 贵,而是材质实例贵。

6. 进阶与验证:Shader Graph对照、序列帧取舍和真机检查

6.1 Shader Graph对照:不用写代码的节点方案

如果项目用 URP 或者团队不熟 CG 语法,Shader Graph 也能搭出同样效果。节点连接思路:UV 节点 -> Subtract 0.5 -> Rotate 节点(输入角度)-> Tiling And Offset 节点(Tiling 连到输入)-> Divide 或 Step 做循环;Time 节点连接到 Tiling And Offset 的 Offset X。光带亮度用 SmoothStep 节点,最后用 Add 和 Multiply 混合颜色,输出连到 Base Color,再开 Surface Type 为 Transparent。对应关系很清晰:Shader Graph 里的 Rotate 就是 3.1 的旋转矩阵,Tiling And Offset 就是 _Tiling 加 _Speed 时间。适合做可调参数多的交互动效,代码方案更适合批量产出和跨版本稳定。

6.2 序列帧与Shader的取舍:包体、内存与效果上限

复杂光效,比如光晕扩散、粒子拖尾、多层折射,优先序列帧;单纯扫光、呼吸光、渐变光,Shader 方案从包体和内存上都甩序列帧几条街。一个 512x512 的 30 帧序列帧,在包里按压缩后也接近 1MB,运行时还要解码进内存;Shader 只是几 KB 的代码加一张静态贴图,而且几乎没有运行时内存波动,也不会遇到粒子特效内存泄露那种长期运行后越积越高的毛病。做 unity 包体优化时,先把活动页面的 Logo 光效从序列帧换成这版 Shader,包体和加载时间都会有体感变化。效果上限当然序列帧更高,但大多数运营需求用不到那个上限。

6.3 验证清单与我的习惯

拿到一个流光效果,我不会只看编辑器。先开 Frame Debugger 确认材质和 Pass 对,再切到 Game 视图把分辨率调到 1080P 和 720P 各看一遍,最后真机跑一圈:Android 上切 OpenGL ES 3.0 和 Vulkan 各检查一次,iOS 上留意 Retina 高分屏下的光带宽度是否过细。颜色上我习惯做一点降饱和,光带不要用纯白,而是取 Logo 主色提亮 20% 到 30%,观感立刻从“廉价特效”变成“品牌质变”。这是我在做了好几个启动屏和活动页之后沉淀下来的习惯,希望能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询