
简介这份Unity Shader资源以Logo流光效果为实战案例面向想要入门Shader编程的开发者与游戏美术解析动态光效从原理到落地的完整实现思路。包内共43个文件以20个asset场景数据、2个shader着色器代码、2个mat材质参数为主体辅以png流光贴图与json工程配置压缩包仅92KB小巧便于直接拉取工程对照学习。资源覆盖纯代码实现与流光图片滑动叠加两条技术路线集中演示了ShaderLab语法、时间变量驱动、UV动画及Lerp混合等关键知识点场景与Mesh文件均已就绪可在Unity中直接运行并观察不同参数下的动态变化。已有1423人学习下载对希望掌握Unity表面着色器并独立制作动态特效的读者而言是一份结构清晰、可直接上手的轻量参考资料。1. 先把Logo流光效果讲明白一张贴图加一个Pass为什么值得做很多做Unity UI、品牌展示页或者大屏启动引导的开发者第一次被问到“让Logo像被一束光扫过”时第一反应是让美术做一段视频或者序列帧。这个思路本身没错但在包体、清晰度和改版需求上都很吃亏。用Unity Shader实现Logo流光效果本质上是片元着色器里做一次UV偏移计算再配合透明度混合。它不需要额外美术资源不改变模型也不依赖任何插件以后想改速度、改颜色、改亮度只需在材质面板上滑几个参数。对做UI框架、二次元Shader、卡通渲染项目的人来说这是一个低成本但质感提升明显的技能点。这篇文章会把原理、可复现代码、调参经验和坑一次讲完。2. 流光效果的原理拆解UV偏移、亮度带与透明度混合怎么配合2.1 为什么选Shader而不是序列帧或视频我见过不少团队把Logo扫光做成一段MP4理由是“美术出片快”。但视频方案有三个绕不开的问题第一包体里多了一段几兆甚至几十兆的资源对包体优化很不友好第二视频分辨率固定放大到不同屏幕会发虚第三客户改一次颜色美术就得重新渲染一次。序列帧稍微好一点但内存占用和播放控制依然麻烦。Shader方案把流光变成一段实时计算任何分辨率下都是锐利的改参数字段级响应这是它能替代前两者的根本原因。如果从渲染开销看一个Logo流光Shader只多了一次纹理采样和几次浮点运算开一个额外的Pass都算浪费。它没有物理模拟没有噪声也没有后处理完全可以用在移动端中低端机器上。真正消耗性能的反而是透明物体之间的排序和过度绘制这两个问题我会在第5章专门讲。先记住一个结论这种效果在GPU上是“便宜”的要在工程里大规模批量使用需要约束的是它挂载的材质数量和不透明遮罩的覆盖范围。实现方案包体占用清晰度调整成本运行时开销视频大受限于分辨率重新出片解码器开销序列帧中受限于帧大小美术改帧内存占用高Shader流光几乎为零任意分辨率锐利滑参数即可极低2.2 条带亮度怎么算把一个数学公式变成视觉观感流光效果的核心难题不是“怎么让光扫过去”而是“怎么让扫过去的光看起来是条光带而不是整张贴图忽明忽暗”。做法通常是在片元Shader里构造一个随时间移动的距离函数先让每个像素的uv.x坐标和当前时间做差得到一个“当前扫光中心线”到该像素的距离再用这个距离映射成0到1的亮度值。距离为0时最亮距离越远越暗。专业一点的说法叫“UV偏移驱动的亮度带”实际上就是一个很便宜的数学公式。float dist abs(i.uv.x - frac(_Time.y * _Speed)); float band 1.0 - smoothstep(_BandWidth - _Softness, _BandWidth _Softness, dist);这段代码是流光Shader的心脏。_Time.y是Unity自带的场景计时器单位是秒乘上_Speed后frac(_Time.y * _Speed)会得到一个0到1之间循环往复的值它代表当前扫光中心线的位置。abs算出当前像素到中心线的横向距离smoothstep把距离映射成亮度距离小于内半径时亮度为1超过外半径时亮度为0中间是平滑过渡。_BandWidth控制光带宽度_Softness控制边缘软硬。为什么这里要用frac而不是直接取余因为frac天然保证中心线每秒钟在0到1区间循环移动不会因为时间太大而漂移出画面。有了band亮度剩下的事情就是决定扫光作用在Logo的哪一块区域上。很多新手直接return band结果整张贴图都亮了这不是流光是频闪。正确思路是用Logo本身的透明通道或者一张单独的Mask图去乘band得到“只在Logo形状内部流动”的光。这就是下一章要落地的代码逻辑。2.3 透明度混合为什么放在最后一步流光不是把Logo整体变亮而是在Logo原有颜色上叠一条更亮的光带。如果直接替换颜色Alpha通道会被破坏半透明边缘会消失。常见做法是把band当作混合因子让最终输出的Alpha等于“Logo原始Alpha × 扫光亮度”再把颜色叠加回去。这里必须打开透明混合通道ShaderLab里的写法是Blend SrcAlpha OneMinusSrcAlpha让输出颜色和帧缓冲里已有的背景色做一次标准透明混合。混合本身是GPU固定管线行为不受片元Shader里其他逻辑影响所以排序问题常常被忽略。我的习惯是把这个Shader的渲染队列设为Transparent关闭深度写入但不关闭深度测试。这样背景的不透明物体仍然会挡住后面的流光而Logo本身又能保持透明属性。有关半透明物体彼此之间的穿插顺序后面避坑章节会专门讲。3. 用Unity Shader实现最小Logo扫光代码逐段拆解与参数含义3.1 一个可直接粘贴的Shader文件下面这版是我在项目里做UI和模型Logo时最常用的一套基础框架。它只依赖一张主贴图可选一张Mask图不要求额外脚本。你把它存成.shader文件工程里创建一个材质把Logo贴图和材质拖进RawImage的Material插槽就能看到效果。Shader Custom/LogoFlowingHighlight { Properties { _MainTex (Logo Texture, 2D) white {} _MaskTex (Flow Mask (Optional), 2D) white {} _FlashColor (Flash Color, Color) (1, 1, 1, 1) _Speed (Flow Speed, Range(-2.0, 2.0)) 0.5 _BandWidth (Band Width, Range(0.05, 0.8)) 0.18 _Softness (Band Softness, Range(0.01, 0.3)) 0.06 _Intensity (Flash Intensity, Range(0.2, 3.0)) 1.0 } SubShader { Tags { QueueTransparent RenderTypeTransparent IgnoreProjectorTrue } Pass { Cull Off ZWrite Off Blend SrcAlpha OneMinusSrcAlpha 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; sampler2D _MaskTex; float4 _MainTex_ST; fixed4 _FlashColor; float _Speed; float _BandWidth; float _Softness; 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 { // 采样主贴图主贴图的Alpha在透明Logo场景下就是形状蒙版 fixed4 texColor tex2D(_MainTex, i.uv); // 单独采样可选Mask白区允许流光出现黑区禁止 fixed mask tex2D(_MaskTex, i.uv).a; // 扫光中心线在uv.x方向循环移动 float stripPos frac(_Time.y * _Speed); // 当前像素离中心线的距离 float dist abs(i.uv.x - stripPos); // 亮度带中心亮边缘软过渡 float band 1.0 - smoothstep(_BandWidth - _Softness, _BandWidth _Softness, dist); // 流光强度叠加加法会让亮部更有阳光照射感 float intensity band * _Intensity * mask; fixed3 finalRGB texColor.rgb _FlashColor.rgb * intensity; // Alpha仍由Logo本体决定流光只在Logo形态内可见 fixed finalAlpha texColor.a; return fixed4(finalRGB, finalAlpha); } ENDCG } } }这个版本有一个值得注意的设计我刻意没有把mask乘到最终Alpha上而是只乘在了RGB的高光上。原因很简单很多Logo贴图是带透明通道的PNGAlpha本身就是Logo轮廓Mask应该只控制扫光的形状范围不应该改变Logo整体的透明度。如果某张Logo图Alpha本来就是全白则需要单独准备一张Mask图下面代码里_MaskTex默认是白色贴图不赋值时所有像素都会通过。关于采样方向o.uv TRANSFORM_TEX(v.uv, _MainTex)保证主贴图的平铺和偏移属性可以被材质面板控制。对Logo场景一般用不到Tiling但保留它可以方便以后在UI编辑器里快速做偏移测试。_Intensity被设计在加法叠加的乘法配额里取值1.0时流光增加一层和_FlashColor等量的颜色取值2.0就会明显过曝这在视觉上反而可能更接近“灯照”的感觉需要按项目风格取舍。3.2 为什么这版能作为最小实现最小两个字不是随便说的。整个Shader不依赖任何C#脚本不创建额外纹理不需要Shader Graph节点网络只靠ShaderLab内置的_Time和一次frac运算就把动画做出来了。运行时DrawCall只增加一个透明材质Pass如果项目里Logo本来就要渲染贴图那么换上这个Shader几乎不增加开销。这套逻辑也适合作为后续做屏幕空间扭曲、边缘发光、NPR风格高光等等效果的地基。如果你直接挂在UI上有两条路在Canvas下用RawImage组件把Material拖到RawImage的Material属性或者用SpriteRenderer直接把Material挂在Renderer上。RawImage更省心因为它的mesh是一张大四边形uv天然是0到1的完整区间不会出现Image组件那种typeSliced把uv切成九宫格的问题。如果项目UI规范里大量使用Image且开启Image的Sliced模式我的建议是先统一把这个Logo流光效果只给RawImage用否则扫光会在九宫格拉伸区域出现不均匀畸变这是很多团队最后弃用Shader方案的真实原因不是Shader不行是挂载对象选错了。万一Logo是模型表面的贴图比如展台上一个带Logo的牌匾这个Shader也可以直接挂。UnityObjectToClipPos已经把模型顶点转到裁剪空间uv来自模型网格的自带UV扫光会沿着模型的x方向跑。你需要做的只是把_Speed调成负值来切换扫光方向其它逻辑一字不用改。这也是这套基础框架在UI和3D场景里通用的原因。4. 让流光有质感Mask选型、边缘柔化和线性空间的关键参数4.1 Mask不只是透明通道准备一张黑白遮罩的三种场景第3章的代码里流光作用范围由mask和texColor.a控制。这里的坑在于并非所有Logo贴图都有Alpha通道。我经历过三种典型情况第一种是透明底PNGAlpha就是Logo轮廓代码里直接用texColor.a当形状蒙版没问题第二种是白底或黑底JPG贴图本身没有Alpha此时如果只依赖texColor.atex2D会返回1.0流光会出现在整个矩形底上非常难看第三种是Logo内部还分区域比如字标部分允许扫光图形部分不允许单靠Alpha区分不了。对于后两种情况常见做法是让美术额外导出一张和主贴图同尺寸的黑白PNG命名为_MaskTex白色区域允许流光出现黑色区域完全屏蔽。在Shader里我把它乘进流光亮度里所以Mask黑色区域连高光都不会有。这张Mask不需要导入设置勾选sRGB因为它不是给人看的颜色贴图是数据贴图建议把Wrap Mode设为Clamp避免UV边缘采样到外部像素压缩格式尽可能选高精度真要压缩就选RGBA Compressed但不能接受大面积颜色条纹。还有一种进阶做法是把Mask嵌进主贴图的Alpha通道而不是单独传一张图。这个方法省一次纹理采样但是当你需要同时使用透明底Logo和局部扫光控制时一个Alpha通道只能表达一件事会互相冲突。我的准则是形状透明用主贴图Alpha扫光范围单独用Mask两者不混用。为一张Logo多出一个贴图资源占内存但换来的是参数解耦后续美术调整不碰Shader。4.2 三个必调参数条带宽度、流转速度和流光强度参数面板一多就容易变成玄学我给几个常用的经验起始值正式项目里应该按Logo的实际宽高比微调。_BandWidth控制亮带在uv.x方向上的宽度取值0.1到0.3比较像光线扫过超过0.4就变成了大范围提亮缺少聚焦感。_Softness必须比_BandWidth小负责柔化亮带两侧边缘调太小时亮带像被刀切过会有严重锯齿调太大时光带糊成一片失去方向性。_Speed建议0.3到0.8UI场景里的Logo扫光一般需要“舒展”而不是“慌忙”的感觉超过1.0会让视觉焦点反复跳跃。参数建议范围给得过大的后果给得过小的后果_BandWidth0.12 ~ 0.30整图闪白失去扫光感像一根窄线几乎看不清_Softness0.02 ~ 0.08光雾化边缘发虚边缘锯齿明显_Speed0.3 ~ 0.8扫动过快观感焦躁像静止高光失去动画意义_Intensity0.8 ~ 1.5RGB被截断颜色发白几乎看不出流光看起来都是简单的滑条但它们之间是相互制约的。_BandWidth越小_Softness越要往低了调否则细光带边缘的软衰减会让它消失。_Intensity越大亮带周围越容易出现一道半透明的外溢光晕这在不透明底Logo上尤其明显。如果项目走卡通渲染或二次元Shader风格我一般把_Intensity控制在1.0以内让流光保留原始Logo的明暗层次而不是粗暴叠加一层白光。还有一个容易被忽略的色彩空间问题。工程在Player Settings里如果开启了Linear颜色空间那么Shader里所有从贴图采样的颜色都要经过一次线性化处理。_MainTex纹理导入面板需要勾选sRGB否则LogoC图在Linear管线里会变亮流光边缘会发灰。Shader里亮度带本身是纯数学计算不受色彩空间影响但最终叠加到sRGB贴图上时伽马空间和线性空间的同一组参数会得到截然不同的对比度。真正的血泪经验是调参数前先确认项目Colorspace是Gamma还是Linear否则你在编辑器和真机看到的颜色永远对不上。4.3 让流光和Logo颜色形成层次而不是互相抢眼高明的Logo流光通常不会用纯白做流光颜色而会取品牌色系里偏亮一点的同类色。比如Logo是深蓝色流光用淡蓝白而不是纯白这样视觉上会觉得光是Logo自身材质被照亮而不是额外加了一盏灯。_FlashColor这个属性在代码里已经预留了可控入口项目里可以把它暴露给策划或美术。还有一种做法是让流光颜色随时间变化比如从白色渐变到品牌色这个需求其实一条lerp就能实现但需要额外暴露两个颜色属性。关于颜色叠加方式的细节我建议加法叠加而不是乘法叠加。乘法会让黑色底上的Logo保持黑色流光到了黑色区域就消失了加法才能产生真正的发光感。如果你觉得加法让亮部细节被盖掉可以把_FlashColor.rgb * intensity的强度改低而不是把混合方式改成混合系数。第3章代码用的就是加法叠加我始终留着一个_Intensity滑条给美术做调节。5. Logo流光避坑手册从渲染队列到精度的5个排查方向5.1 流光把Logo整块照亮了没有扫过效果现象是最直观的挂上材质后整个Logo被均匀提亮根本没有光带移动或者只有很微弱的一条线在闪。排查时先看_BandWidth的数值如果美术把它拖到了0.8smoothstep的衰减范围几乎覆盖整个uv.x区间视觉上就是整张贴图一起亮和真实扫光完全不像。再看_Softness是否大于等于_BandWidth这种情况会让smoothstep的起止参数颠倒band函数退化成常量或三角尖峰。原因本质是亮度带公式的两个参数互相越界。把_BandWidth调回0.2左右确保_Softness取它的三分之一左右问题通常会立刻消失。如果调完仍然整图闪那就要怀疑Mask的问题当_MaskTex没有赋值时它默认是白色此时流光确实会覆盖整个矩形区域这是设计如此。先在材质面板给_MaskTex选择一张黑白遮罩再观察效果。我排查这类问题的习惯是临时在片元函数末尾写一句return fixed4(band.xxx, 1)把band直接可视化输出屏幕上能看到亮带说明公式和UV没问题看不到说明问题出在UV坐标或主贴图平铺上。5.2 光带方向倾斜、倒着扫或者横竖反了现象光带明明应该从左扫到右结果却是从上扫到下或者扫到一半方向反转。原因基本不在Shader本身而是UV坐标的含义和你想的不一样。Unity UI的RawImage组件uv原点通常在左下角u向右v向上这没太大争议。但你如果把它挂在SpriteRenderer上又使用Sprite图集图集打包会把原始Sprite裁剪到图集内的局部区域此时uv不再保证是0到1完整区间可能出现uv.x只在0.3到0.7之间变化的局面光带扫动的速度就和你设定的速度不一致了。还有一种很常见的现象是同一个Shader挂在不同Logo上有的从左扫到右有的从右扫到左。这通常是贴图本身的UV方向或者模型坐标轴翻转引起的。解决方法是先确认你的需求到底是“所有Logo统一一个方向”还是“跟随Logo模型方向”。如果是前者把Shader里的i.uv.x替换成1.0 - i.uv.x即可反过来扫。如果是后者应该去调整模型的镜像轴而不是在Shader里打补丁。我之前在某大屏项目里因为客户要求倒着扫直接做了一版方向可开关的变体反而比反复约束美术规范更省事。5.3 半透明物体之间穿插错乱Logo被按钮或面板吃掉现象Logo的流光出现后它和旁边的半透明按钮、弹窗之间的层级关系突然乱了有时Logo从面板后面透出来有时前面的按钮被Logo盖住。原因在于这个Shader关闭了ZWrite而透明物体之间是按绘制顺序排序的不按Transform和Hierarchy的层级来。项目里Canvas下的UI默认通过CanvasRenderer的层级控制一旦有独立材质或不同Canvas出现绘制顺序就可能和面板顺序脱节。解决方向有几个。最直接的方案是给Logo单独分一个Canvas把Sorting Order设得足够大让它永远显示在其它UI元素之上。但要清楚一个代价单独Canvas意味着需要一次额外的Canvas重建和可能一次额外DrawCall。另一个方案是让流光Shader不做透明混合把Logo放在专用的不透明相机层面这样不会参与透明排序但这会牺牲透明Logo的需求。如果场景里只有UI元素相互穿插优先尝试Tags里的Queue从Transparent改成Overlay直接把渲染顺序推到最后大多数情况下能立竿见影。5.4 安卓真机扫光边缘出现闪烁跳变现象在编辑器里流畅的扫光打包到安卓中端机上以后亮带的边缘会出现一格一格的阶梯跳动或者像坏像素一样有杂色闪点。这不是贴图精度问题而是GPU精度问题。Unity的移动端Shader编译器经常会把float精度的变量自动降成half或fixed当_Time.y数值较大时frac(_Time.y * _Speed)在half精度下会丢位导致扫光中心线位置出现肉眼可见的跳跃。解决方式是把关键变量强制声明成float代码里dist和stripPos这两个变量不要用half修饰片元函数内的高频数学运算保持在float下进行。如果项目仍出现类似问题还有一个辅助技巧不直接用_Time.y的原始大数值而是先取模一次例如float t fmod(_Time.y, 3600.0);这样时间被限制在1小时内避免了浮点数尾数过大造成的精度损失。另外_Speed为负时表达式_Time.y * _Speed会得到负数frac对负数是向下取整的结果仍然在0到1之间不会有逻辑反问题但有些旧驱动的实现可能对负参数处理不一致保守做法是用saturate配合正向速度。5.5 材质参数被批处理打断DrawCall翻倍现象场景里同时挂了几十个Logo用的是同一个材质但Frame Debugger里每个Logo都单独占了1个DrawCall没有合批。原因是透明材质如果含有自定义属性且这些属性没有写入CBUFFERURP的SRP Batcher就无法识别材质变体会退回到老式合批通道遇到非同一网格或材质实例就会打断合批。这个问题的表象是流光效果本身没毛病但性能分析的时候你会突然发现UI开销暴涨。解决方式是按照Unity标准做法把Shader里所有暴露的float属性和纹理属性对应的具体变量放入CBUFFER_START(UnityPerMaterial)块中材质实例属性才能被SRP Batcher缓存。一个常见错误是只把纹理放进Properties、变量声明丢在CG代码外面这在新版URP下几乎必然打断合批。如果你的项目还在用内置渲染管线这个问题不一定会出现但建议养成写CBUFFER的习惯后续迁URP不用返工。CBUFFER_START(UnityPerMaterial) float4 _MainTex_ST; fixed4 _FlashColor; float _Speed; float _BandWidth; float _Softness; float _Intensity; CBUFFER_END这里要特别强调_MainTex_ST的位置。_MainTex_ST虽然不直接暴露在业务参数面板里但它也是每实例属性漏掉它照样会打断合批。把这个CBUFFER块放在CG代码的变量声明区之后保持声明命名和Properties一一对应批处理就恢复正常了。如果是移动端的老GPU还需要注意PackedArray和实例化宏不过对Logo流光这种低频UI元素SRP Batcher已经是主战场先把CBUFFER补齐再说。6. 从单条流光到组合玩法双流光、闪烁叠加与效果验证基础Shader跑通以后很多人会不满足于单条光带想要更丰富的表现。最合理的进阶方向是双流光叠加两条扫光带分别以不同速度、不同方向扫过Logo视觉上会形成交错的光感。实现上不需要另写Shader只要把band的计算逻辑复制一遍用第二次_Time.y和新的参数控制然后把两个band的强度相加再和_FlashColor相乘。两条光带速度差明显一点比如0.4和0.9能避免相位重叠后出现视觉静止的尴尬时刻。float stripPos2 frac(_Time.y * _Speed * 2.0 0.5); float dist2 abs(i.uv.x - stripPos2); float band2 1.0 - smoothstep(_BandWidth * 0.8 - _Softness, _BandWidth * 0.8 _Softness, dist2); float combinedBand saturate(band * 0.7 band2 * 0.5);另一个能让Logo“活”起来的组合是流动高光加呼吸闪烁。呼吸闪烁和扫光不冲突它影响的是整个Logo的亮度基准。在片元Shader里加一个_FlickerColor用sin(_Time.y * 2.0) * 0.5 0.5做呼吸因子叠加到最终RGB和Alpha上。这样Logo在没有扫光经过时也有微弱的明暗变化适合放在直播间、展厅大屏这类需要“Logo一直在呼吸”的场景里。但呼吸效果要克制幅度控制在15%以内否则会抢走用户对主体的注意力。验证一个流光Shader是否真正合格不能只看编辑器里那几秒。我的常规检查动作有三步第一在Game视图把分辨率和DPI调到目标真机最高档观察光带边缘是否出现锯齿或抖动第二打开Frame Debugger确认这个Transparent材质没有打断同一UI图集下的合批DrawCall增量不超过1第三在真机上连续运行20分钟让_Time.y累积到比较大的数值验证前面说的精度问题在项目所支持的最低端机型上是否复现。如果三条都通过这个Shader基本就可以放心固化到UI框架里。最初做第一个Logo流光时我把_BandWidth调到了0.5觉得光带越宽越明显结果换来的效果是整张Logo像被手电筒直射和廉价广告屏没有区别。后来把宽度压到0.15、速度放到0.4、强度控制在0.9才突然理解了“少即是多”这句话在Shader调参里的意义。流光不是一个需要张扬的特效它的任务是让Logo在观众视野里多停留那一秒。希望这篇文章能帮你少走这段弯路也祝你的Logo流光效果一次就能跑出想要的感觉。本文还有配套的精品资源点击获取