1. 项目概述为什么动态UV滚动是Shader的“必修课”在Unity的Shader开发里动态UV贴图循环滚动几乎可以算作一个“Hello World”级别的进阶效果。乍一听不就是让贴图动起来吗但当你真正上手从简单的单向滚动到复杂的多向、变速、条件控制你会发现这小小的功能背后串联起了ShaderGraph几乎所有核心节点的用法。它不仅是实现水面流动、岩浆涌动、传送门光效、瀑布背景等游戏视觉效果的基石更是理解UV坐标、时间变量、数学运算和节点逻辑的绝佳训练场。很多朋友卡在Shader入门就是因为理论懂了但不知道如何用节点“拼”出一个具体效果。今天我就以一个从业多年的TA技术美术视角带你从最基础的原理开始手把手拆解如何用ShaderGraph实现从简单到复杂的动态UV滚动并分享那些官方文档里不会写的调试技巧和性能优化心得。2. 核心原理拆解UV坐标与时间的舞蹈在深入节点之前我们必须把底层逻辑吃透。动态UV滚动的本质是随时间变化对模型的UV坐标进行持续的数学偏移。2.1 UV坐标的本质是什么你可以把UV坐标想象成一张世界地图的经纬度。任何一张2D贴图无论它看起来多复杂在Shader眼里都是一个被规范在(0,0)到(1,1)这个正方形区域内的数据集合。U代表横向经度V代表纵向纬度。当我们将这张“地图”包裹到一个3D模型上时模型上的每个顶点都会对应地图上的一个(U,V)坐标从而决定这个顶点应该显示贴图上的哪个颜色。注意这里常有一个误区认为UV坐标就是模型的屏幕像素位置。完全不是。UV是模型顶点自带的属性在建模阶段由美术人员拆分好它决定了3D模型与2D贴图的对应关系与模型在场景中如何移动、旋转、缩放无关。2.2 “滚动”是如何发生的滚动就是让这个对应关系动起来。假设我们想让贴图沿着U方向横向滚动逻辑就是在每一帧为每个顶点的原始U坐标加上一个随时间增大的偏移量。 公式非常简单New_U Original_U Time * Speed这里的Time是ShaderGraph提供的一个从游戏开始不断累加的秒数Speed是我们控制滚动快慢的系数。当New_U超过1时它就会“溢出”到下一周期的0-1范围内如果贴图在边缘处是连续无缝的那么视觉上就会形成无限循环滚动的效果。V方向的滚动同理。2.3 无缝贴图Wrapping的关键这是实现“循环”而非“滑动”的前提。在导入贴图时必须确保其Wrap Mode设置为“Repeat”重复。这样当UV坐标值大于1或小于0时贴图就会像地板砖一样重复平铺而不是拉伸边缘像素Clamp模式。通常用于滚动的贴图本身在美术制作时左右或上下边缘就需要是连续可拼接的。3. ShaderGraph基础实现创建你的第一个滚动Shader理论清晰后我们进入实战。打开Unity创建一个新的Unlit Graph对于简单的纹理滚动Unlit足够且高效。3.1 构建节点网络获取基础数据在空白处右键创建Texture 2D属性节点命名为_MainTex这是我们的滚动贴图。创建Sample Texture 2D节点将_MainTex连接上去。此时节点上的UV引脚默认使用模型自带的UV0。引入时间与速度创建Time节点。它会输出一个包含多个时间变量的结构体我们通常使用Time节点下的Sine Time或直接使用Time节点输出的t总时间。对于匀速滚动用t即可。创建Vector 2属性节点命名为_ScrollSpeed。这个二维向量的X分量将控制U方向速度Y分量控制V方向速度。将它的默认值设为(0.5, 0)表示先实现横向滚动。计算动态UV创建Multiply乘法节点。将Time节点的t输出口与_ScrollSpeed连接得到随时间增长的偏移向量(t * Speed.x, t * Speed.y)。创建Add加法节点。将Sample Texture 2D节点上当前的UV输入即模型原始UV与上一步计算出的偏移向量相加。这一步就是核心的UV UV Time * Speed。将计算好的新UV重新连接回Sample Texture 2D节点的UV输入口。完成输出将Sample Texture 2D节点的RGB输出连接到Master Stack或PBR Master的Base Color。保存Graph将其赋给一个材质球再赋给场景中的一个Plane或Quad。运行游戏你应该能看到贴图稳定地向左滚动因为UV坐标增加贴图像素向左移动。3.2 基础实现的注意事项速度的正负与方向Speed值为正时UV坐标增加贴图向左U负方向或下V负方向滚动。如果你想让它向右或向上滚动将速度值设为负值即可。性能初探Time节点是一个全局只读变量几乎无消耗。每帧对每个顶点执行一次向量加法开销极低。这个基础版本的性能瓶颈几乎可以忽略不计是移动设备友好型操作。材质参数调节将_ScrollSpeed暴露为材质属性后你可以在运行时动态修改它实现加速、减速甚至反向滚动的效果无需修改Shader代码。4. 进阶控制技巧让滚动效果充满设计感基础滚动太单调接下来我们通过组合节点实现更高级、更可控的效果。4.1 实现双向与圆周滚动双向独立滚动这我们已经实现了。只需给_ScrollSpeed的X和Y分别赋予不同的值贴图就会沿对角线方向滚动。例如(0.3, 0.1)。圆周滚动旋转效果这不是简单的UV偏移而是需要对UV坐标进行旋转矩阵变换。创建Rotate节点或在Procedural分类下找。将模型原始UV连接到Rotate节点的In端口。创建Multiply节点用Time的t乘以一个_RotationSpeed标量属性得到旋转角度弧度制。将此角度连接到Rotate节点的Rotation端口。将旋转后的UV输出给Sample Texture 2D。实操心得直接旋转UV可能会导致贴图在中心扭曲。更好的做法是先将UV中心平移到(0.5,0.5)旋转后再平移回去。这需要额外的Subtract和Add节点。对于Quad这类中心对称模型影响不大但对于复杂模型需要留意。4.2 使用正弦函数创造“来回摆动”效果匀速滚动缺乏动感。我们可以用Sine节点让滚动速度呈波形变化模拟呼吸、波浪起伏的感觉。创建Sine节点。将Time的t乘以一个_Frequency频率属性后输入。将Sine的输出范围在[-1,1]乘以一个_Amplitude振幅属性得到摆动的偏移量。将这个偏移量一个标量或构建成Vector2加到原始UV上。你还可以将Sine的输出通过Remap节点重新映射到(0,1)范围确保UV不会回退。这个技巧非常适合制作悬浮光点、缓慢波动的能量场等效果。4.3 基于距离或深度的渐变滚动让滚动速度根据像素到某个点如摄像机、世界中心的距离或屏幕深度发生变化可以创造出更立体的空间感。基于距离使用Position节点Object Space或World Space获取片元位置。使用Distance节点计算位置到某个参考点可定义为一个Vector3属性的距离。使用Divide或Multiply节点用这个距离去影响_ScrollSpeed。例如FinalSpeed _BaseSpeed / (Distance 1.0)这样离参考点越远滚动越慢。基于深度Z轴使用Scene Depth节点获取当前像素的深度值。同样用此深度值通过Remap或平滑函数处理后去调制滚动速度。可以实现类似“远处水流慢近处水流急”的视觉效果。4.4 多图层混合滚动单一贴图滚动有时显得单薄。我们可以叠加多层不同速度、不同方向的滚动贴图混合出更丰富的细节。创建第二个Texture 2D属性和对应的Sample Texture 2D节点。为第二层贴图创建独立的_ScrollSpeed2和UV偏移计算逻辑。使用Lerp线性插值或Add、Multiply节点来混合两层采样结果。Lerp需要一个混合系数float属性Add需要注意颜色值可能超过1导致过曝通常需要乘以一个小于1的系数。更高级的混合可以使用Blend节点模拟Photoshop中的叠加、正片叠底等混合模式。5. 性能优化与常见问题排查效果实现了但作为项目实战我们必须考虑性能和稳定性。5.1 性能优化要点贴图采样次数是最大开销每个Sample Texture 2D节点都是一次纹理采样。基础版本只有1次性能最佳。多图层混合时采样次数成倍增加。在移动平台上务必严格控制。慎用复杂数学运算Sine、Distance、Length等运算在片段着色器Fragment Shader中对每个像素执行数量巨大。如果效果可以接受考虑在顶点着色器Vertex Shader中计算动态UV然后插值到片元。在ShaderGraph中你可以通过将UV计算节点连接到Vertex阶段的Position或自定义节点来实现但这属于更高级的用法。利用纹理的Mipmap对于在屏幕上尺寸会变化的滚动物体确保贴图启用了Mipmap可以显著减少远处物体的纹理采样开销和锯齿。避免不必要的精度在保证效果的前提下将Float精度改为Half可以减少GPU寄存器的压力和带宽占用。5.2 常见问题与解决方案实录以下是我在项目中实际踩过的坑和解决方法问题现象可能原因排查与解决方案贴图滚动时有明显的“跳变”或“闪烁”1. 贴图Wrap Mode不是Repeat。2. UV偏移计算中使用了Time的t其值过大导致浮点数精度丢失。1. 检查导入的贴图设置将Wrap Mode改为Repeat。2. 使用Frac或Fraction节点对Time * Speed的结果取小数部分。因为UV在0-1间循环整数部分无意义。公式变为UV Original_UV Frac(Time * Speed)。这是实现完美无缝循环的关键技巧滚动速度不稳定时快时慢1.Time节点选择错误。Delta Time是帧间隔用于匀速运动会导致帧率依赖。1. 确保使用Time节点的t(Total Time) 或Sine Time作为时间基准它们是自游戏开始的总时间与帧率无关。在模型边缘或特定角度滚动停止或扭曲1. 模型UV展开不正确存在重叠或超出[0,1]范围。2. 使用了世界空间或视图空间坐标计算UV但模型发生了非均匀缩放。1. 检查3D模型的UV布局。在建模软件中确保UV在0-1象限内且无重叠。2. 对于依赖空间坐标的进阶效果尽量在着色器开始阶段将坐标归一化或考虑使用模型空间坐标以减少缩放影响。多图层混合后颜色过亮过曝混合方式使用Add且未对结果进行归一化。将Add改为Lerp进行透明度混合或使用Multiply进行正片叠底。如果坚持用Add在最后连接Base Color前通过Clamp节点将RGB值限制在[0,1]范围内。在URP中效果正常但切换到Built-in管线或反之无效ShaderGraph创建时选择的渲染管线模板不匹配。URP和Built-in RP以及HDRP的ShaderGraph节点库和Master Stack有差异。为目标管线重新创建Graph或使用可编缉的Render Pipeline兼容版本。5.3 调试技巧可视化中间数据当节点网络复杂效果不如预期时学会调试至关重要。ShaderGraph内置了优秀的调试工具端口预览将鼠标悬停在任何一个节点的输出端口上会在材质预览球上实时显示该端口的计算结果如UV、颜色、数值。这是定位问题节点的最快方法。创建自定义调试输出你可以临时将任何一个中间计算向量如动态UV连接到Master Stack的Emission自发光或Base Color直接观察它的可视化结果。例如将UV直接输出为颜色可以清晰地看到UV是如何在整个模型表面分布和变化的。使用Split和Combine节点如果一个Vector2的UV计算结果有问题用Split节点将其拆分成单独的U和V分量分别进行调试看是哪个分量计算有误。6. 实战案例制作一个高级动态水面材质让我们综合运用以上所有技巧创建一个相对复杂的水面Shader。基础层使用一张深蓝色法线贴图以中等速度沿世界XZ方向滚动模拟基础水流方向。将法线贴图采样结果连接到Normal输入。细节层使用第二张尺度更小、对比度更高的法线贴图以更快的速度沿略微不同的方向滚动叠加在第一层法线上通过Normal Blend节点增加水面的高频细节。滚动调制使用Scene Depth和Smoothstep节点根据水面与岸边或水底的深度差动态降低岸边区域的滚动速度模拟浅水区流速慢的效果。泡沫与高光使用一张泡沫噪声图其UV滚动速度是基础层的1.5倍。用Depth差值和水面法线朝向Fresnel Effect节点共同控制泡沫图的显示强度Alpha连接到Emission和Smoothness实现岸边泡沫和波光粼粼的高光。顶点偏移为了有波浪起伏将其中一张法线贴图的RG通道代表XY方向的斜率通过Simple Noise扰动后输入到Vertex阶段的Position偏移上注意幅度要小避免模型穿插。这个案例涵盖了多图层、速度调制、基于深度的条件控制、顶点动画等多个高级概念。搭建过程中务必分阶段测试先确保基础水流滚动正确再逐一添加细节层、调制效果和顶点动画每步都通过端口预览功能验证中间结果。Shader开发是一个高度依赖迭代和调试的过程。动态UV滚动作为其中最经典、最灵活的效果之一掌握其从原理到进阶的全套玩法就等于打通了ShaderGraph节点化思维的任督二脉。记住所有复杂的效果都是由Add、Multiply、Time这些最简单的节点组合而成的关键在于你如何用它们来描述你想要的视觉逻辑。多动手试错善用调试工具你的Shader技能树会因此点亮一大片。