
简介一套Unity Shader示例资源演示了如何在游戏引擎中为标志或界面标题制作动态扫光效果。资源面向有一定材质基础、希望上手着色器语言与纹理动画的开发者。压缩包共43个文件整体约92KB包含2个着色器源码、2个材质文件、20个资源文件、场景文件与工程配置导入后即可运行对照当前已有1422人学习浏览。资源的核心在于两种实现思路其一为纯代码方式在着色器中编写表面函数与顶点片元逻辑利用时间变量、插值和混合运算让高光沿标志表面流动其二为图片叠加方式通过改变纹理坐标驱动贴图周期滑动同时将环绕模式设为重复形成循环扫光。此外还给出可视化节点编辑方案降低手写代码门槛完整工程目录清晰便于逐步修改参数观察效果变化适合学习着色器调试、纹理坐标动画和材质混合。1. 用 Shader 给 Logo 做流光不写循环动画也能让画面“活”起来Logo 流光效果在游戏里太常见了——启动界面、技能图标、活动入口一道光从图标表面扫过去整个界面立刻显得“贵”了。但如果你用传统方式做这个动画无非是切序列帧或者用 Animation 拖一张图片的位置帧数一多体积极速膨胀。Shader 方案的核心思路完全不同把时间变量交给 GPU由片段着色器在每个像素上计算“此刻这道光该亮在哪里”CPU 根本不参与逐帧运算。这份 FlowLight 工程包的核心就是两套实现——纯 ShaderLab 代码流光和流光贴图 UV 滑动后者适合 Shader 新手快速上手前者适合要做多段光、异形 Logo 的进阶场景。读完这篇你能从原理到参数把两者都跑通顺便避开我踩过的几个真坑。2. 纯代码实现流光ShaderLab 里把 UV、时间与颜色算明白2.1 为什么选 ShaderLabCPU 动画与 GPU 计算的本质区别先摆一个结论用 Animation 拖 UV 偏移也能做流光但那种方式每个物体的动画数据都要 CPU 逐帧更新场景里几十个带流光的 Logo 同时出现时Profile 面板上的 CPU 耗时会被拖起来。Shader 方案把计算下放到 GPU运行时只改一个全局时间值就行。FlowLight 工程里的纯代码实现走的是 Unity 的标准 Unlit Shader 改法不是表面着色器Surface Shader原因是流光效果不需要参与光照计算用 Surface Shader 会多出无谓的光照处理开销。你只需要在顶点着色器里把模型坐标转成裁剪坐标剩下的全部交给片元着色器。另外要提一下Cull Off这行。很多游戏 Logo 是带厚度的 3D 模型背面也要扫过流光时必须保留双面渲染如果只是平面 Quad 上的图标改成Cull Back反而能省一点片元计算量。工程代码默认开了双面原因很简单贴 Logo 的面片经常出现在 UI 弹窗的边缘角度一转背面露馅会很难看。2.2 完整 Shader 代码从属性定义到片元计算的逐行拆解这里我直接给出工程里流光线部分的核心代码去掉了无关的环境反射和阴影分支Shader Custom/LogoFlowLight { Properties { _MainTex (Logo 主贴图, 2D) white {} _FlowColor (流光颜色, Color) (1, 1, 1, 1) _FlowSpeed (流光速度, Range(0.1, 5.0)) 1.0 _FlowWidth (流光宽度, Range(0.1, 2.0)) 0.5 _FlowAngle (流光角度, Range(0, 360)) 45 } SubShader { Tags { QueueTransparent RenderTypeTransparent } Blend SrcAlpha OneMinusSrcAlpha Cull 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 _FlowColor; float _FlowSpeed; float _FlowWidth; float _FlowAngle; 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 旋转到流光方向 float rad radians(_FlowAngle); float2 rotatedUV; float2 center float2(0.5, 0.5); rotatedUV.x (i.uv.x - center.x) * cos(rad) - (i.uv.y - center.y) * sin(rad) center.x; rotatedUV.y (i.uv.x - center.x) * sin(rad) (i.uv.y - center.y) * cos(rad) center.y; // 时间驱动的光带位置 float pos rotatedUV.x _Time.y * _FlowSpeed; // 用 smoothstep 生成软边光带 float band smoothstep(pos, pos - _FlowWidth, rotatedUV.x); // 与主贴图叠加 col.rgb band * _FlowColor.rgb; col.a 1.0; return col; } ENDCG } } }这段代码的核心逻辑分三段。第一段在顶点着色器里只做坐标转换和 UV 的 Tile/Offset 处理TRANSFORM_TEX的作用是把主贴图的 Tiling 和 Offset 面板参数应用到 UV 上如果你的 Logo 贴图在材质面板里设置过 Tiling这里的取值才不会失真。第二段是 UV 旋转。_FlowAngle是流光方向的倾斜角默认 45 度。旋转中心取 UV 的 (0.5, 0.5)也就是贴图正中间。这个旋转不是必须的——如果你能接受流光永远水平扫过可以把 rotateUV 这段全部删掉直接拿i.uv.x参与计算。但实际项目里 Logo 往往不是正方形斜向光带比水平光带更自然所以我保留了这个参数。第三段是光带计算。_Time.y是 Unity 内置时间变量单位是秒在 CG/HLSL 里可用。rotatedUV.x _Time.y * _FlowSpeed让判定值随帧数单调递增相当于一条斜线在 UV 空间里向右平移。smoothstep(pos, pos - _FlowWidth, rotatedUV.x)的写法是这张图的细节smoothstep 的两个边界参数可以反过来写这样生成的不是一条从 0 到 1 的过渡而是一条限定宽度的光带而且边缘是软的。如果这里用 step() 硬边函数光带两侧会有明显的像素锯齿放大后非常丑。最后把光带的灰度值乘以流光颜色叠加到主贴图的 RGB 上。col.a 1.0是因为 Blend 开了透明度混合但 Logo 本身通常是不透明的强制 alpha 为 1 可以避免边缘透出背景的残留。2.3 参数如何调速度、宽度、角度的经验范围工程里给了三个 Range 属性我实际调过的经验值如下表参数常用范围翻车表现_FlowSpeed0.52.0大于 3 会让光带变闪小于 0.3 看起来像卡住_FlowWidth0.20.8大于 1.0 时整张图全被照亮没有“扫过”感_FlowAngle090超过 90 后光带方向反转注意正负号这里的数值单位是 UV 空间不是像素。之所以说“常用范围”而不是固定值是因为最终观感跟贴图分辨率有关——1024×1024 的 Logo 和 256×256 的 Logo同样的_FlowWidth在人眼里的宽度完全不同。我一般会先把速度调到 1.0用 Game 视图实际跑一遍再按观感微调而不是一上来就把三个参数全拉满。提示如果 Logo 贴图带透明通道PNG 透明底记得 Shader 的 Tags 必须包含QueueTransparent否则透明区域会以黑色方块的形式显示。这是 Shader 新手最容易忽略的一行。3. 流光贴图叠加滑动一张 repeat 纹理搞定循环动画3.1 什么时候该用贴图方案而非纯代码纯代码方案虽然干净但有一个绕不过去的限制光带的形状是数学函数定义的。工程里的代码实现只有一条“直线光带”你能改的只是宽度、角度和颜色。如果设计稿里的流光是一条带渐变的曲线或者尾部有粒子拖曳纯代码要么写很复杂的函数要么直接做不到。贴图方案的思路完全换了一条路把流光本身做成一张带透明度的纹理让它贴着 Logo 表面滑动。美术或者程序生成一张 1024×512 的流光 PNG剩下的工作只是控制它的 UV 怎么动。打开 FlowLight 工程包里的 Assets/walk_light 场景跑起来就是一个完整的贴图流光示例。这个方案对 Shader 不熟的人非常友好因为真正需要理解的只有一个点UV 坐标怎么随时间移动。3.2 UV 滑动的核心脚本Update 里改 offset 的注意点贴图方案的运行时代码长这样using UnityEngine; public class FlowLightScroll : MonoBehaviour { public Material flowMaterial; [Tooltip(流光贴图的流动速度每秒移动几个 UV 单位)] public float scrollSpeed 0.5f; [Tooltip(0 表示水平方向1 表示垂直方向)] public int scrollAxis 0; private Vector2 offset; void Update() { // 逐帧累加偏移超出 1 后自动回到起点 offset flowMaterial.GetTextureOffset(_FlowTex); if (scrollAxis 0) offset.x scrollSpeed * Time.deltaTime; else offset.y scrollSpeed * Time.deltaTime; // 取小数部分保证纹理在 Repeat 模式下无缝循环 offset.x Mathf.Repeat(offset.x, 1.0f); offset.y Mathf.Repeat(offset.y, 1.0f); flowMaterial.SetTextureOffset(_FlowTex, offset); } }这个脚本的关键点在于GetTextureOffset和SetTextureOffset的配合。每次 Update 里先把当前偏移读出来再累加新值最后写回去——如果你每次都写成固定值流光会永远不变。Mathf.Repeat(offset, 1.0f)是为了把偏移限制在 0 到 1 区间配合纹理的 Wrap Mode 设为 Repeat贴图划过边缘时直接从头出现循环无缝。这里有一个很多人会写错的细节Time.deltaTime必须乘以速度系数而不是直接在offset.x 0.5f。乘以 deltaTime 之后动画速度与帧率无关60 帧和 30 帧的机器上流动速度一致。如果一帧一帧地加固定值高刷屏上流光会明显跑得更快。3.3 贴图与 Shader 的配合Repeat 模式与采样参数贴图方案里流光纹理能不能循环取决于它的 Wrap Mode。导入设置里要手动改两处设置项值作用Wrap ModeRepeat允许 UV 超过 0~1 后重复采样Filter ModeBilinear光带边缘柔和避免马赛克感Wrap Mode 改完还不够Shader 里的采样语句也需要对应支持。工程里的 Shader 片段大致如下Shader Custom/FlowLightMultiTex { Properties { _MainTex (Logo 贴图, 2D) white {} _FlowTex (流光贴图, 2D) white {} _FlowColor (流光染色, Color) (1, 1, 1, 1) } SubShader { Tags { QueueTransparent RenderTypeTransparent } Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc sampler2D _MainTex; sampler2D _FlowTex; float4 _FlowTex_ST; fixed4 _FlowColor; struct v2f { float2 uv : TEXCOORD0; }; fixed4 frag (v2f i) : SV_Target { fixed4 main tex2D(_MainTex, i.uv); // 用偏移后的 UV 采样流光贴图 fixed4 flow tex2D(_FlowTex, i.uv * _FlowTex_ST.xy _FlowTex_ST.zw); // 只取流光的 alpha 作为遮罩颜色用 _FlowColor 控制 fixed3 finalColor main.rgb _FlowColor.rgb * flow.a; return fixed4(finalColor, main.a); } ENDCG } } }这段代码里_FlowTex_ST.zw是材质面板上 FlowTex 的 Offset 值脚本通过SetTextureOffset改的正是这两个值。i.uv * _FlowTex_ST.xy _FlowTex_ST.zw是TRANSFORM_TEX的展开形式写成这样是为了让你看清 UV 偏移到底加在哪里。用flow.a而不是flow.rgb也是工程里的一个细节流光贴图的 RGB 通常是白色或浅色真正的形状信息全在 alpha 通道里。这样美术在做贴图时只需要关心 alpha 画得对不对颜色统一交给材质的_FlowColor调整不用重新导出贴图。注意如果流光贴图没有 alpha 通道比如 JPGflow.a会恒为 1整张图全部亮起完全看不出扫过效果。务必导出带透明通道的 PNG或者在 Material 里把 Surface Type 改为 Transparent 后确认导入设置中的 Alpha Is Transparency 已勾选。4. 用 Shader Graph 搭节点把速度、宽度、颜色做成可视化参数4.1 Shader Graph 与手写 Shader 的边界什么时候值得用Shader Graph 是 Unity 2018 系列开始内置的可视化 Shader 工具对流光这种节点不多的效果特别友好。手写 ShaderLab 的问题是语法敏感少一个分号或者写错一个变量名面板上直接报错新手找了个把小时找不到错在哪。Shader Graph 把纹理采样、时间、数学运算全变成节点逻辑一目了然改参数不需要重编译。但我得说句实话Shader Graph 不是万能的。它的节点图最后会被 Unity 翻译成代码翻译结果与你手写的版本在性能上基本一致但两条路径生成的变体集合略有差别。如果项目有大量移动端最低配置机型我建议纯代码方案为主如果团队里有人不熟悉 ShaderLab或者需要快速迭代效果Shader Graph 是更稳的选择。如果你的工程里没有现成的 Graph 版本按下面的连法五分钟也可以自己搭一个。4.2 节点连线图从属性声明到输出的一整套连接Shader Graph 里做流光加贴图的流程大致分为四步。第一步创建 Unlit Shader Graph 资产新建以下 Property 节点属性名类型默认值作用MainTexTexture2D无Logo 主贴图FlowTexTexture2D无流光贴图必须设 RepeatFlowSpeedFloat0.5流动速度FlowWidthFloat0.3光带宽度FlowColorColor白色流光颜色第二步连时间节点。创建 Time 节点输出 Time 即秒数拖出 Multiply 节点把 Time 和 FlowSpeed 相乘。第三步创建 Lerp 节点作为 UV 偏移的基础。Lerp 的 A 端口接 (0,0)B 端口接 (1,0)T 端口接速度与时间的乘积。这样 UV 的 x 分量会从 0 平滑过渡到 1然后 Repeat 模式让它回到起点等效于脚本里的 Mathf.Repeat。如果要让光带带一点扭动感可以在 Lerp 后面串一个 UV Distortion 节点输入端口接向量强度值它会按噪声扰乱 UV。做 Logo 流光时强度一般控制在 0.05 以内否则光带会抖成水波纹。第四步最终颜色相加。把 MainTex 采样结果、FlowTex 的 alpha 分量、FlowColor 全部连到 Add 节点上最后输出到 Base ColorMainTex 的 alpha 直接接 Alpha 输出端口。这组节点的顺序其实隐藏了一个数学关系Lerp 的输出作为 UV 输入给 FlowTex 采样器等于每帧都在改纹理坐标效果与脚本改 offset 完全等价但完全不占 CPU。所有运算都在 GPU 的顶点和片元阶段完成。4.3 在预览面板验证一张贴图一个 slider 快速看效果Shader Graph 左侧有 Preview 窗口默认反射展开一张 Sphere 模型。做 Logo 流光时预览球体并不合适——光的扫过区域对曲面和平面差别很大。我一般会先在 Scene 里放一个 Quad 拖到预览窗口的模型槽位然后给 MainTex 赋上实际 Logo 贴图。验证时手动拖 Property 里的 FlowSpeed 滑块观察光带移动是否连续、宽度是否合适。这比手写 Shader 的调参方式高效得多因为不用改代码、等编译、看效果三步循环。需要做的检查只有三个光带方向是否正确、扫过边缘是否出现硬边、透明区域是否发黑。这三类问题在第 5 章里有对应排查办法。提示Shader Graph 的 Repeat 模式不是自动继承贴图导入设置的。如果 FlowTex 在资产面板里设置为 Clamp图中第 3 步的 Lerp 循环会失效流光划过一半就消失。检查贴图导入设置这一行往往能解决一半的“效果不对”问题。5. 流光特效常见问题与排查从黑屏到方向不对的六个坑5.1 流光贴图变成全白方块现象材质预览里整个 Logo 全亮成纯白色看不到任何光带扫过只在背景里看到一个巨大的白色矩形。原因贴图没有 alpha 通道或者导入设置里 Alpha Is Transparency 没有勾选。flow.a拿到的是整张图的最大不透明度等于 1光带遮罩失效相当于把流光贴图的覆盖范围整体提亮。解决检查 .png 的源文件是否带透明通道。在 Unity 资产面板选中贴图看 Inspector 的 Alpha Source 如果是 None说明通道已丢失需要让美术重新导出带 alpha 的 PNG 或 TGA勾选 Alpha Is Transparency 后重新 Apply再把 Shader 里采样代码的flow.a换成flow.r把形状信息存在红色通道也可以但工程默认用 alpha。排查顺序我一般是这样先在材质预览里双击流光贴图看它自身是否带透明信息。如果贴图看着没问题再往 Shader 的后端走用 Frame Debugger 看输出值。多数情况下问题出在导入设置的 Alpha 通道处理上而不是 Shader 写错。5.2 流光不动或速度忽快忽慢现象画面静止光带像冻住一样或者同一个工程在 144Hz 显示器和 60Hz 显示器上流动速度差出一大截。场景里其他动画都正常唯独流光表现异常。原因UV 偏移没有用Time.deltaTime做帧率补偿而是每帧加固定值。笔记本外接高刷屏时最容易暴露这个问题同一台机器在不同刷新率下光带速度都不同。纯代码方案里如果误写了_Time的某一分量也会出现类似现象——_Time.x是 1/20 秒的恒定值不是秒数。解决脚本方式统一使用offset.x scrollSpeed * Time.deltaTimeShader 方式直接依赖_Time.y它本身就是与帧率无关的。写完代码后在 Player Settings 里把 Target Frame Rate 分别改为 30 和 60 各跑一遍光带速度一致才算通过。5.3 Logo 透明区域出现黑色边缘或方框现象Logo 是圆形的但流光扫过时四周出现黑色矩形圆形边缘还有一圈半透明的墨迹。原因Shader 的 Tags 里缺少QueueTransparent和Blend SrcAlpha OneMinusSrcAlpha透明通道没有参与混合默认按不透明物体渲染黑色背景直接显示。墨迹是 Blend 模式错误导致的比如 SrcAlpha 和 OneMinusSrcAlpha 写反。解决SubShader 里加上透明队列并确认 Blend 模式为Blend SrcAlpha OneMinusSrcAlpha。如果项目用的是 URP还要在 Render Pipeline 资产里确认 Transparent 队列没有被剔除。检查时可以临时把 Logo 贴图的 alpha 通道显示为红黑遮罩能更直观地看到哪个环节把透明信息丢了。5.4 光带方向逆了或者角度不对现象设计稿里斜向左下扫实际跑出来是斜向右上扫怎么调 _FlowAngle 都不对。原因UV 旋转的三角函数方向与预期的坐标系不一致。Unity 的 UV 坐标系 y 轴向上但有些美术用 DCC 工具导 UV 时习惯把左上角当原点两者正好镜像。另一个隐藏原因是贴图在导入设置里开了 Generate Mip Maps 且 Border 设置为 Clamp边缘采样会让光带在边界处反向。解决把_FlowAngle取负值或者在旋转矩阵的 sin 项手动加负号。不要盲改角度值先确定你的贴图坐标系习惯——用一张带文字的 Logo 图在 Scene 里旋转看方向是最快的验证方式。5.5 纯代码 Shader 编译报错 syntax error 但找不到错在哪现象Console 面板报错对应到某一行但肉眼检查那行没毛病甚至在别处新建文件抄一遍同样的代码却编译通过。原因常见于中文字符串或全角符号混入 ShaderLab 代码。把工程里的 Shader 复制到 VS Code 时注释里的中文引号会被转成全角Unity 的 Shader 编译器不接受任何非 ASCII 的符号。全角空格尤其恶心它显示成普通空格但编译时直接报错。解决把注释全部改成英文或者确保保存文件的编码是 UTF-8 无 BOM。#pragma fragment和_Time.y附近如果有全角空格也会触发这类报错建议整段代码重新敲一遍而不是复制粘贴。5.6 移动端低端机型上出现闪烁或撕裂现象桌面平台一切正常打包到部分安卓机型后流光扫过区域偶尔闪烁严重时有横向撕裂。原因透明材质没有设置Queue与RenderType的一致性是第一梯队怀疑对象更深一层是低端机型 GPU 对浮点数精度不够Shader 的fixed类型在部分 Adreno 驱动上被降级导致smoothstep边界计算抖动。解决把 Shader 里的fixed4、fixed3全部换成half4、half3保持中等精度即可。同时给 SubShader 增加Lighting Off和ZWrite Off减少深度缓冲的写入竞争。如果还闪把光带计算挪到顶点着色器让片段着色器只做采样和混合这属于终极手段但确实最稳。提示上面六条里我实际栽过最多的是第三和第五。透明队列这一行经常在复制 Shader 时被漏掉而全角符号的报错会让你怀疑人生。遇到 Shader 编译错误先检查注释和引号再看 Tags最后才去查语法。6. 让流光更像产品级效果渐变、混合与多段流光的调优技巧流光效果做到“扫不过去”的阶段只完成了一半剩下的一半是让它看起来不像测试版。这里分享三个我常用的调优技巧都是在工程默认参数基础上的小幅修改。第一个技巧光带加边缘衰减。上一章的 smoothstep 已经产生软边但宽度超过 0.5 时中间会有一段饱和度平台。改成band * (1.0 - abs(rotatedUV.x - pos) / _FlowWidth)可以让每条光带呈现中间亮、两端渐隐的线性衰减视觉上更像真实的光柱扫过而不是一块色条。这个公式的处理量只有一个减法加一个除法顺手补进 shader 里没有压力。第二个技巧叠加两层不同速度的流光。Logo 界面里单条光带有时显得寡淡工程代码可以复制一个 Pass或者在同一片元里加一次采样。两束流光速度差 0.3、角度差 20 度叠加后会产生一种光来回交错的高级感。这种做法用脚本方案很难实现因为两个 offset 要分别控制而纯代码方案只需要多定义一组速度、角度、颜色属性。第三个技巧用 GrabPass 或环境色映射流光颜色。默认_FlowColor是恒定值光带颜色始终一样。产品项目里常见做法是给 _FlowColor 加一个低饱和度的 HDR 色同时把混合模式从直接 Add 改成Blend SrcAlpha One让流光在暗色背景上轻微透出底下的颜色而不是把 Logo 的一整条区域全部盖死。验证方法很直接把场景里所有动态 UI 元素静默单独观察流光这段时间内的表现。如果半分钟内没有出现闪烁、断裂、透明边缘异常这套效果就可以提交到正式分支。从那以后我每次做流光都会强制走一遍这条流程先确认 Wrap Mode 是 Repeat再确认透明队列没漏最后才开 Game 视图看动效——这三个顺序反过来你会在排查上多花一倍时间。希望帮到你。本文还有配套的精品资源点击获取