ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Unity Shader流光效果实战:Logo扫光动画与性能优化

Unity Shader流光效果实战:Logo扫光动画与性能优化 简介这份Unity Shader教程资源围绕Logo流光效果展开面向游戏开发者、Unity初学者及有一定Shader基础想要提升画面表现力的用户。资源共43个文件包含shader源码、材质mat、场景unity、贴图png、项目配置json与asset等类型压缩包仅92KB轻量易用便于直接导入项目或对照学习。已有1420人学习浏览说明该主题在社区中具有较高实用度。内容完整覆盖纯代码实现与流光贴图叠加滑动两种方案讲解ShaderLab中的表面函数、顶点/片段着色器以及Time、Lerp、Blend等语法要点也涉及纹理坐标UV动画、Wrap Mode Repeat循环设置借助Shader Graph可视节点控制流光速度与扭曲可快速复现流畅的Logo扫光特效并灵活延伸至按钮、标题等场景。1. 主界面那个Logo凭什么吸引你多看两秒游戏主界面、App启动页、活动弹窗凡是需要传达品牌感的位置几乎都放了一个 Logo。静态贴图太死板拖一个序列帧动画又占包体、还得美术逐帧切图。最常见的做法是用 Shader 在片元着色器里算一条随时间移动的高光带扫过 Logo 表面形成“流光”质感。它成本极低一个材质、一个贴图、一个时间变量就能让美术静态图“活”过来。这个标题里真正值钱的部分不是那几行 Shader 代码而是你如何理解 UV 坐标、时间驱动和混合模式这三者的配合。UV 决定光带扫过哪里_Time 决定它怎么动Blend 决定光带是和原图叠加还是替换。本文按“UV 动画 → 流光实现 → 贴花与交互 → 优化”的顺序展开中间直接给可抄的代码和参数表。适合场景UI 组件的 Logo 特效、3D 模型的 Glow 材质、以及面试前临时抱佛脚时快速过一遍 Shader 基础。2. 流光效果的着色器地基UV 坐标与 _Time 的配合2.1 为什么所有扫光效果都从 UV 开始贴图采样必须要有坐标这个坐标在 Shader 里就是i.uv。它的取值范围通常是[0, 1]左下角是(0, 0)右上角是(1, 1)。流光条本质上是一段“在 UV 空间里移动的渐变带”你改变的不是贴图而是采样时用的坐标。对 UV 做加减乘除就能让贴图的内容在屏幕空间上产生位移、缩放和旋转。最常见的做法是把时间信号加到 UV 的 x 分量上fixed4 frag (v2f i) : SV_Target { // 让uv.x随时间向右移动实现贴图横向平铺滚动 float2 uv i.uv; uv.x _Time.y * _Speed; fixed4 col tex2D(_MainTex, uv); return col; }这段代码的逻辑很好懂_Time.y是自场景加载以来的累计时间单位秒乘以_Speed得到偏移量再加到uv.x上。每过一秒贴图就沿 x 方向移动_Speed个 UV 单位。注意uv.x超出[0, 1]后tex2D的默认行为是平铺重复wrap mode 为 Repeat所以贴图会循环滚动。真正的流光效果不会让整张贴图滚来滚去而是只让一小段高光区域扫过。要实现这一点得把“高光位置”从时间的函数里解出来然后和 UV 做比较。这就要求你先把 UV 动画的只读性理解透——UV 只是采样坐标它本身没有颜色、没有能量颜色是tex2D函数根据坐标从贴图里取出来的。你完全可以在采样前把 UV 和任意一个值做比较结果存进一个 float然后再决定输出什么颜色。2.2 用 step 和 smoothstep 切出高光带如果你把uv.x当成一个从 0 到 1 的线性变量那么“光带”就是这段直线上某个区间内值为 1、区间外值为 0 的方块波。step(edge, x)能从某个位置开始置 1两个 step 相减就是一条有宽度的光带float band step(_Center - _HalfWidth, uv.x) * (1 - step(_Center _HalfWidth, uv.x));_Center是光带中心线的 UV 坐标_HalfWidth是光带半宽。两个 step 的差值产生一个只在[_Center - _HalfWidth, _Center _HalfWidth]区间内为 1、其余位置为 0 的值。把这个值和 Logo 主贴图相乘就只在高光带范围内显示亮色。但 step 的边缘是一条硬边扫过去是“啪”地一下出现、又“啪”地一下消失观感上太生硬。step硬边、smoothstep软边流光要的是软边。smoothstep(edge0, edge1, x)会在两个边界之间做 Hermite 插值返回值从 0 平滑过渡到 1。把上述减法换成 smoothstep光带的两个边缘就有了淡入淡出的渐变区。软边还有个附带好处边缘锯齿少放大后的 Logo 流光不会出现像素闪烁。2.3 旋转变换让光带不总是横平竖直Logo 排版千奇百怪光带只沿水平方向扫会显得单调。把 UV 绕某个中心点旋转一个角度再做水平方向的带型计算流光就可以任意角度扫过。UV 旋转矩阵公式固定直接记住float2 rotateUV(float2 uv, float2 center, float angle) { uv - center; float s sin(angle); float c cos(angle); uv float2(uv.x * c - uv.y * s, uv.x * s uv.y * c); uv center; return uv; }调用时传一个固定角度_Angle返回的 uv 用于后续的带型判断。此时带本身仍然是水平的但因为你已经旋转了整个坐标空间视觉上它就是一条斜向扫过的光带。这张表总结了 3 个核心变量对效果的直接作用变量作用域参数变化效果_Time.y全局时间值越大流光移动越快_Center带中心位置由时间加上初始偏移决定_Angle旋转角度0 为水平90 度为垂直45 度斜扫3. 可复用的完整 Logo 流光 Shader代码、参数与混合模式3.1 一套可直接挂到 UI 上的 Shader 源码下面这段 Shader 是我项目里最常改的底子适用于 Unity 内置渲染管线的 UI 组件Image、RawImage。它把主贴图、流光贴图、高光带三者合成Shader Custom/LogoFlowLight { Properties { _MainTex (Logo Texture, 2D) white {} _FlowTex (Flow Gradient, 2D) white {} _FlowColor (Flow Color, Color) (1,1,1,1) _Speed (Flow Speed, Range(0.1, 5)) 1.0 _Width (Flow Width, Range(0.05, 0.8)) 0.2 _Angle (Flow Angle, Range(0, 360)) 0 _Intensity (Flow Intensity, Range(0, 3)) 1.0 [Toggle] _UseFlowTex (Use Gradient Tex, Float) 1 } SubShader { Tags { QueueTransparent RenderTypeTransparent IgnoreProjectorTrue } Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Cull Off Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc sampler2D _MainTex; float4 _MainTex_ST; sampler2D _FlowTex; fixed4 _FlowColor; float _Speed; float _Width; float _Angle; float _Intensity; float _UseFlowTex; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float4 screenPos : TEXCOORD1; }; v2f vert (appdata v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); o.screenPos ComputeScreenPos(o.pos); return o; } float2 rotateUV(float2 uv, float2 center, float angle) { uv - center; float s sin(angle); float c cos(angle); uv float2(uv.x * c - uv.y * s, uv.x * s uv.y * c); uv center; return uv; } fixed4 frag (v2f i) : SV_Target { float2 uv i.uv; // 角度转弧度逆时针旋转UV坐标系 float rad _Angle * 0.0174533; // 角度转弧度 float2 rotUV rotateUV(uv, float2(0.5, 0.5), rad); // 流光中心线在uv.x上移动 float center frac(_Time.y * _Speed); // 0到1循环 float band smoothstep(center - _Width, center - _Width * 0.3, rotUV.x) * (1 - smoothstep(center _Width * 0.3, center _Width, rotUV.x)); // 采样Logo原图 fixed4 mainTex tex2D(_MainTex, uv); // 采样流光渐变贴图提供更丰富的高光层次 fixed flowTexCol 1.0; if (_UseFlowTex 0.5) { float2 flowUV float2(rotUV.x, 0.5); // 只用渐变贴图的横向渐变 flowTexCol tex2D(_FlowTex, flowUV).r; } // 混合光带颜色原图 * (1 - band) 高光 * band float3 flowColor _FlowColor.rgb * band * flowTexCol * _Intensity; float3 finalColor mainTex.rgb flowColor; finalColor saturate(finalColor); // Alpha保持主贴图的透明不让高光带本身变成不透明块 return fixed4(finalColor, mainTex.a); } ENDCG } } FallBack UI/Default }这段代码的逻辑改写顺序值得注意先取 UV旋转再做带型判断最后才采样贴图并叠加。把一个简单的光带做成可调角度、可调宽度的工程再整合进 UI 的透明度通道。Alpha通道完全继承_MainTex.a意味着高光带只在 Logo 本身不透明的地方可见这样扫到透明区域就不会穿帮。3.2 参数怎么设才不翻车_Angle用的是角度不是弧度写死在代码里的0.0174533是 π/180。如果你希望旋转中心不在图片中心需要把rotateUV调用里第二个参数从float2(0.5, 0.5)改成你的目标 UV 点。_Width的 Range 上限是 0.8超过这个值光带会覆盖几乎整个 Logo效果接近整体闪烁而非扫动。_UseFlowTex开关建议默认开启颜色层次更丰富如果美术只想要纯色光带关掉它就能省掉一次纹理采样。提示frac(_Time.y * _Speed)保证 center 永远落在 0 到 1 之间但光带是有宽度的如果_Width * 2超过 1就会出现两个光带同时出现的瞬间。微信小游戏等轻量场景下这个现象会很明显。4. 中间环节最关键流光遮罩贴图与 C# 端控制4.1 用 Mask 贴图把光限制在 Logo 轮廓里上一节的代码里若 Logo 贴图本身是矩形的从[0,1]范围算出来的光带也会矩形高光扫过整张图包括透明区域效果会有“生硬”观感。更精细的做法是让美术出一张 Mask 贴图白色区域是 Logo 实心部分黑色区域是透明部分。采样后相乘流光就被限制在白色区域边缘锐利度和遮罩图分辨率一致不会出现半透明灰边的光。Mask 采样代码放进 3.1 的 Shader 中需要新增一个属性_MaskTex (Mask Texture, 2D) white {}片元中把光带结果乘以tex2D(_MaskTex, uv).r。主贴图透明区域已经通过mainTex.a控制了最终 AlphaMask 额外控制的是“光能不能点亮这块像素”。美术出图时注意Mask 的边缘颜色要干净不要带灰度过渡否则流光边缘会出现“脏”的半透黑边。4.2 C# 脚本控制流光开始、停止与进度Shader 本身只在渲染时计算它不认识游戏状态。要在点击按钮后触发一波流光或者让流光在某个 UI 弹窗打开时才播放就得用 C# 改材质属性。常见做法是给 UI 对象挂一个控制脚本using UnityEngine; using UnityEngine.UI; [RequireComponent(typeof(RawImage))] public class LogoFlowController : MonoBehaviour { public Material logoMaterial; public float flowSpeed 1.0f; private bool isFlowing false; public void StartFlow() { isFlowing true; // 重置时间偏移让流光从起始位置开始 logoMaterial.SetFloat(_Speed, 0); logoMaterial.SetFloat(_Speed, flowSpeed); } public void StopFlow() { isFlowing false; logoMaterial.SetFloat(_Speed, 0); } public void SetProgress(float value) { // 外部传入0~1进度控制流光位置 logoMaterial.SetFloat(_Progress, value); logoMaterial.SetFloat(_Speed, 0); } void Update() { if (isFlowing) { // 手动累加Shader中的进度方便精确控制 float progress logoMaterial.GetFloat(_Progress); progress Time.deltaTime * flowSpeed; if (progress 1f) progress - 1f; // 循环 logoMaterial.SetFloat(_Progress, progress); } } }上面这段脚本采用了不依赖_Time驱动的方案在 Shader 里新增一个_Progress属性将片段着色器中的center改为直接等于_Progress。好处是你可以暂停、回放、定位到任意进度不受帧率影响。如果你的 Shader 没有_Progress属性SetFloat会失败并打印警告这是最常见的坑——记得先在 Shader 里声明同名属性。参数联动配合表如下脚本方法Shader 变量最终效果StartFlow_Speed恢复大于 0流光持续循环StopFlow_Speed置 0流光停在当前帧位置SetProgress_Progress置 0~1手动调整光带位置4.3 UI 组件的坑Material 实例化与点击穿透把 Shader 挂到 RawImage 的 Material 上时编辑器里直接拖材质是安全的。但运行时若用GetComponentRawImage().material mat;Unity 会实例化一份材质你用logoMaterial.SetFloat改的是实例不是原资产。用 SharedMaterial 则所有引用同一材质物体的 UI 一起变化适合批量 Logo 统一控制。另外涉及“unity 如何扩大按钮的点击范围”这一点在流光 Logo 上同样适用流光材质不会拦截 UI 点击但RawImage默认的raycastTarget是开启的。如果 Logo 上面叠加了按钮而 Logo 的光带影响了视觉层次玩家可能会误点。关闭 Logo 的raycastTarget再把按钮的点击区扩大就能做到“好看不挡路”。这也是 UGUI 场景里最容易被忽略的细节。5. 性能优化、移动端精度与 Unity 游戏优化实践5.1 像素级循环函数很贵用乘法代替分支片元着色器里if分支在不同 GPU 上表现差异巨大尤其移动端 Mali 系列。上面的代码里用了两次smoothstep它是个纯算术函数没有分支这是它比if (uv.x center) ... else ...更快的原因。但如果美术要求光带边缘带随机颗粒感用frac(sin())类伪随机函数做扰动时要注意移动端half精度下三角函数误差大。尽量避免在片元里写for循环每多一次循环就是多一次取的未定义指令。5.2 变体数量别失控如果没有[Toggle]关键字Shader 里的#pragma multi_compile每多一个关键字就是双倍变体。3.1 里我用了[Toggle] _UseFlowTex这种声明会产生_USEFLOWTEX_ON关键字两套变体开和关。如果再加一个[Toggle] _UseMask变体变成 4 套。在“unity游戏优化”语境下Shader 变体是包体膨胀的隐形杀手建议用#pragma shader_feature_local替代multi_compile它可以控制关键字只在本地生效减少全局变体列表。5.3 移动端精度的三个建议第一所有float改成half对大多数移动 GPU 显著提升性能但_Time.y数值很大低精度下会损失精度导致流光移动出现阶梯感所以时间变量保持float其它计算用half。第二saturate后颜色不会超过 1但叠加光带后高光区域会发白这是物理上的高光溢出美术常要求保留纹理细节所以代码里finalColor saturate(finalColor);不是最完善的可以改成finalColor mainTex.rgb flowColor * (1 - mainTex.rgb);做能量守恒的叠加避免纯白死区。第三WebGL 平台下_Time精度依赖浏览器帧同步后台切换标签页再回来自_Time.y会跳变导致流光瞬间甩到另一边用 C# 控制_Progress方案能规避这一点这也对应了搜索词“unity 发布 webgl 使用 idbfs 写入失败”与 Shader 无关但同属发布阶段容易踩的性能坑。5.4 最后的验证手段Frame Debugger 看 RenderPass验证流光的最终效果与其盯着 Game 窗口肉眼找问题不如打开 Window → Analysis → Frame Debugger截一帧后逐个查看每个 Draw Call确认这份 Shader 的 Pass 只执行了一次且 Blend 状态是你的预期。如果发现不透明物体被画在 UI 前面、或者流光被半透明排序挡住检查Queue是否设置成了Transparent还有ZWrite Off是否生效。UI 组件的 Canvas 默认是 Transparent 排序Shader 的渲染队列必须与之匹配否则同一 Canvas 下的多个 UI 元素会出现错误的遮挡关系。本文还有配套的精品资源点击获取
返回列表