尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Unity Shader Graph 水体流动实战:Flow Map 原理与 UV 偏移避坑指南

Unity Shader Graph 水体流动实战:Flow Map 原理与 UV 偏移避坑指南 1. 水体流动效果的核心思路与方案选型1.1 为什么选择 Flow Map 而不是滚动 UV刚接触水体效果的朋友第一反应往往是给水面贴图加一个随时间递增的 UV 偏移也就是常说的UV Scroll。这个做法确实简单一行Time节点乘上速度再Add到UV上就完事了。但只要你真的把它放进场景里跑一圈就会发现两个致命问题一是水面看起来像一条匀速传送带缺乏自然的缓急变化二是当水面有弯曲、分叉或者遇到岸边障碍物时贴图会直接穿模完全没有“绕流”的感觉。Flow Map 解决的正是这个痛点。它的本质是一张方向贴图用 R 通道存 U 方向的速度分量G 通道存 V 方向的速度分量每个像素告诉 Shader“这一点的水应该往哪个方向流、流多快”。这样一来河道拐弯处的水会顺着弯道走遇到石头会自然分流静水区和急流区可以共存于同一张水面。这就是为什么但凡稍微讲究一点的水体效果几乎都会上 Flow Map。从实现成本看Flow Map 在 Shader Graph 里的节点数量其实并不多核心逻辑就是“采样方向图 → 转成偏移量 → 叠加到 UV → 采样噪声图 → 输出”。真正让人翻车的地方不在主逻辑而在UV 偏移的相位处理。这也是我在标题里特意加上“UV 偏移避坑指南”的原因——后面会专门用一整节来讲这个坑。1.2 整体节点架构拆解在动手连节点之前先把整个 Shader 的数据流在脑子里过一遍比盲目拖节点效率高得多。我习惯把 Flow Map 水体拆成四层方向层采样 Flow Map得到每个像素的流动方向向量。时间层用Time节点驱动相位但要经过Frac处理形成循环。扰动层用两层不同缩放、不同速度的噪声图叠加制造水面的细节波纹。合成层把扰动后的 UV 结果采样噪声再乘上颜色、透明度最终接到Master Stack。这四层里方向层和时间层是 Flow Map 的骨架扰动层决定水面的“质感”合成层决定它和场景的融合度。很多人做出来的水像塑料布问题基本都出在扰动层只用了一张噪声图缺少双层叠加带来的视差感。提示Flow Map 的方向向量通常以 0.5 为中性值存储也就是说贴图里 RGB(128,128,0) 代表“不流动”。采样后需要先做一次Multiply 2 - 1的映射把 [0,1] 还原成 [-1,1] 的方向向量这一步千万别漏。1.3 方案对比三种常见水体流动实现为了让你更清楚 Flow Map 的定位我把常见的三种方案拉出来对比一下方案核心原理优点缺点适用场景UV ScrollUV 匀速偏移节点极少性能最好无方向变化穿模严重直线河道、远景水面双层 UV Scroll两层不同速度叠加有一定细节感仍无方向控制湖泊、海面远景Flow Map方向图驱动 UV方向可控绕流自然需要额外方向贴图河道、溪流、近景水面从表格能看出来Flow Map 多出来的成本主要就是一张方向贴图。这张图可以用工具烘焙也可以手绘甚至用简单的渐变加噪声凑合。对于近景水体这点成本完全值得。2. Flow Map 原理与 UV 偏移的相位陷阱2.1 Flow Map 的通道编码与采样映射Flow Map 这张图说白了就是把“水流方向”这个二维向量塞进一张贴图的两个通道里。业界通用的约定是 R 通道存 X 方向对应 UG 通道存 Y 方向对应 VB 通道一般留空或者存强度遮罩。因为贴图通道的取值范围是 [0,1]而方向向量需要 [-1,1]所以存储时做了一次(dir 1) / 2的编码读取时就要反向做dir * 2 - 1的解码。在 Shader Graph 里这个解码过程用一个Multiply加一个Subtract就能完成。具体来说采样Sample Texture 2D得到 RGBA取 RG 两个通道先Multiply一个 2再Subtract一个 1得到的就是真正的方向向量。这一步如果忘了做水流方向会全部偏到第一象限看起来就是所有水都往右上角流非常诡异。方向向量拿到之后还要乘上一个流动强度参数。这个参数控制整体流速通常给个 0.1 到 0.5 之间的值比较合适。给太大水面会像开了倍速给太小又看不出流动。我一般会把它暴露成Float属性方便在材质面板上实时调。2.2 相位循环为什么你的水会“跳帧”现在到了最关键的部分也是标题里说的“UV 偏移避坑指南”的核心。很多人第一次做 Flow Map节点连完发现水面每隔一两秒就“啪”地跳一下像视频卡帧一样。这个问题困扰过我整整一个下午最后才想明白是相位没有做循环处理。原理是这样的Flow Map 的流动逻辑是UV 方向 * 时间 * 速度。时间Time是无限增长的那么偏移量也会无限增长。当偏移量超过 1 的时候UV 采样就会从贴图的另一端重新开始但因为方向是任意的这个“重新开始”的位置和原来的位置对不上于是就产生了跳变。解决办法是引入一个循环周期。具体做法是把时间除以周期取小数部分Frac再乘回周期。这样时间就在 [0, 周期] 之间循环偏移量永远不会超过一个周期对应的距离跳变自然就消失了。用节点表达就是Time → Divide (周期) → Frac → Multiply (周期) → 得到循环时间这个循环时间再拿去乘方向和速度就能得到平滑循环的偏移量。周期一般设 2 到 5 秒太短会看出循环感太长又浪费。注意Frac节点在 Shader Graph 里叫Fraction别找错了。另外周期值不能为 0否则会除零水面直接变黑。2.3 双层噪声叠加制造视差感单层噪声采样出来的水面怎么看都像一张会动的壁纸缺乏立体感。真正让水面“活”起来的是两层噪声以不同速度、不同缩放叠加。第一层缩放小、速度快负责大面积的流动趋势第二层缩放大、速度慢负责细碎的波纹细节。两层叠加之后因为速度不同会产生一种微妙的视差看起来就像水在往深处流。具体参数上我一般这样配第一层 Tiling 给 1 到 2速度给 0.3第二层 Tiling 给 3 到 5速度给 0.15。两层都用同一张噪声图就行没必要准备两张。叠加方式用Add或者Lerp都可以Add出来的对比度更强Lerp更柔和看你要什么风格。这里有个小技巧第二层的 UV 可以在第一层的基础上再叠加一个固定的偏移比如(0.5, 0.5)这样两层的图案不会完全重合细节会更丰富。这个偏移量不用太大0.1 到 0.5 之间就行。3. Shader Graph 节点实操全流程3.1 属性面板与基础设置打开 Shader Graph先建一个Blank Shader Graph然后在 Graph Inspector 里把 Material 设成Unlit因为水体效果通常不需要参与光照计算用 Unlit 性能更好也更容易控制最终颜色。如果你想让水接受场景光照那就选Lit但节点会复杂一些新手建议先从 Unlit 入手。接下来在 Blackboard 里建几个属性这是让 Shader 可调的关键FlowMapTexture2D方向贴图。NoiseTexTexture2D噪声图。FlowSpeedFloat默认 0.3整体流速。FlowStrengthFloat默认 0.2方向强度。LoopDurationFloat默认 3循环周期。BaseColorColor默认浅蓝水体基色。AlphaFloat默认 0.8透明度。这些属性建好之后在材质面板上就能实时调不用每次改代码重新编译效率高很多。特别是LoopDuration和FlowSpeed这两个调的时候盯着 Scene 视图看很快就能找到合适的值。3.2 方向采样与循环时间节点连接先处理方向层。拖一个Sample Texture 2D节点把FlowMap接进去输出取 RG 通道。然后接Multiply 2再Subtract 1得到 [-1,1] 的方向向量。这个向量再乘上FlowStrength方向层的输出就准备好了。接着处理时间层。拖一个Time节点输出接Divide另一个输入接LoopDuration。Divide的输出接Fraction再接一个Multiply另一个输入还是LoopDuration。这样得到的循环时间就在 [0, LoopDuration] 之间来回跑。这个循环时间再乘上FlowSpeed得到最终的偏移量。把方向层的输出和偏移量相乘就得到了每个像素的 UV 偏移向量。这个向量再Add到原始的UV上就是流动后的 UV。到这里Flow Map 的核心逻辑就完成了节点数量其实不到十个。3.3 噪声采样与颜色合成有了流动 UV接下来采样噪声图。拖两个Sample Texture 2D节点都接NoiseTex但 UV 输入不同。第一个直接接流动 UV第二个接流动 UV 乘一个 Tiling 系数再加一个固定偏移。两个采样结果用Add叠加得到最终的噪声值。这个噪声值接下来要参与颜色计算。我一般用Lerp把BaseColor和噪声值混合噪声亮的地方颜色浅一点暗的地方深一点模拟水面的高光变化。混合系数给 0.3 到 0.5 之间比较自然。最后把结果接到Master Stack的Base ColorAlpha接上透明度属性一个基础的水体流动 Shader 就成型了。提示如果水面看起来太“平”可以在噪声叠加之后再加一个Power节点把噪声的对比度拉高水面的明暗变化会更明显。Power的值给 1.5 到 2 之间就行。3.4 完整节点清单速查为了方便你对照检查我把整个 Shader 用到的节点列成一张表节点数量作用Sample Texture 2D3采样方向图和两层噪声Time1提供时间驱动Divide / Fraction / Multiply各 1构成循环时间Multiply / Subtract各 1方向向量解码Add2UV 偏移叠加、噪声叠加Lerp1颜色混合Property 节点7暴露可调参数照着这张表连基本不会漏节点。连完之后记得在材质面板上把FlowMap和NoiseTex两张贴图拖进去不然水面会是纯色。4. 常见问题排查与实战避坑技巧4.1 水面跳帧、闪烁、方向错乱速查实际做的时候问题往往不是节点连错而是参数没配对。我把踩过的坑整理成一张速查表现象可能原因解决方法水面每隔几秒跳一下相位没做 Frac 循环加 Fraction 节点设 LoopDuration所有水往同一方向流方向图没做 2-1 解码补 Multiply 2 和 Subtract 1水面闪烁噪声图 Tiling 过大把 Tiling 降到 5 以下水面完全不动FlowSpeed 为 0 或贴图没接检查属性值和贴图槽水面发黑LoopDuration 为 0 导致除零把 LoopDuration 设成大于 0边缘穿模方向图边缘没有过渡在方向图边缘做渐变遮罩这张表里的每一条我基本都亲自踩过。特别是第一条“跳帧”当时排查了半天以为是 Time 节点的问题最后才发现是少了Fraction。所以你现在看到标题里的“避坑指南”真不是凑字数。4.2 性能优化与移动端适配Flow Map 水体的性能开销主要来自三次贴图采样。在 PC 上这完全不是问题但如果是移动端就得注意了。我的经验是移动端把两层噪声减成一层方向图分辨率降到 256 甚至 128LoopDuration和FlowSpeed用常量而不是属性这样能省下不少开销。另外Sample Texture 2D节点默认是带 Mipmap 的如果水面在远处Mipmap 会自然降低采样精度这其实是好事。但如果你发现远处水面糊成一片可以在贴图导入设置里把 Mipmap 关掉或者调高各向异性过滤。这个取舍看你的场景需求。还有一个容易被忽略的点水面的渲染队列。如果水面和场景其他透明物体有穿插记得在 Master Stack 里把Render Queue设成Transparent并且把Depth Write关掉不然会出现奇怪的遮挡关系。4.3 让水面更真实的三个加分项基础效果跑通之后想让水面再上一个档次可以试试这三个方向。第一是加法线扰动用噪声图采样出来的值去扰动法线水面会有细碎的高光闪烁特别适合阳光下的溪流。第二是加边缘泡沫用深度图或者场景位置算出一个边缘遮罩在靠近岸边的地方叠加白色泡沫真实感立刻拉满。第三是加颜色渐变根据水深或者流速改变水的颜色浅处偏绿、深处偏蓝视觉层次会丰富很多。这三个加分项都不难法线扰动就是在噪声采样后接一个Normal From Height节点边缘泡沫需要一个Scene Depth节点做差颜色渐变用Lerp根据一个遮罩混合两种颜色就行。等你把基础 Flow Map 玩熟了再往上加这些会发现整个水体的表现力完全不一样。我在实际项目里做河道水体的时候最开始就是裸的 Flow Map跑起来总觉得差点意思。后来加了法线扰动和边缘泡沫同一个场景美术那边直接说“这水活了”。所以基础打牢之后别急着收工往上叠细节才是拉开差距的地方。
返回列表