
1. 水体流动效果的核心思路与方案选型1.1 为什么选择Flow Map而不是顶点动画做过水面效果的人大概都经历过这个阶段一开始用顶点动画让水面上下起伏看起来还行但总觉得少了点“流动感”。后来尝试用UV滚动简单粗暴地让贴图往一个方向平移结果水面像传送带一样机械重复一眼假。再后来接触到Flow Map才算是找到了一个在效果和性能之间比较平衡的方案。Flow Map的本质是一张记录了方向向量的贴图。它的RG通道分别存储了U方向和V方向的偏移量取值范围通常在0到1之间映射到实际偏移量时需要转换到-1到1的区间。这张图告诉Shader每个像素应该往哪个方向流动流动的强度是多少。相比全局统一的UV滚动Flow Map能让水面不同区域有不同的流动方向比如靠近岸边的地方流速慢、方向偏转河道中央流速快、方向一致这种空间变化的流动感才是真实水体的样子。从性能角度看Flow Map方案的核心开销就是一次额外的贴图采样和几次向量运算对于移动端来说完全可以接受。相比之下如果用顶点动画做波浪顶点数不够效果就粗糙顶点数上去了性能又吃不消。而Flow Map是在片元层面做文章跟模型复杂度解耦这也是它在实际项目中更受欢迎的原因。1.2 Shader Graph在这个流程里的角色Shader Graph是Unity的可视化Shader编辑工具从2018版本开始逐渐成熟到2022 LTS已经相当稳定了。用它来做Flow Map水体效果有几个明显的好处一是节点连线直观调试的时候可以实时看到每个节点的输出不用反复改代码编译二是参数暴露方便美术同学可以直接在材质面板上调流速、调方向、调颜色不需要程序介入三是跨管线兼容URP和HDRP都能用同一套逻辑迁移成本低。当然也有代价。Shader Graph生成的代码在极端情况下可能不如手写HLSL精简但对于水体流动这种中等复杂度的效果来说差异可以忽略不计。而且Shader Graph支持自定义函数节点真遇到性能瓶颈也可以把关键计算用HLSL封装进去灵活度是够的。1.3 整体方案拆解整个效果可以拆成四个层次来理解。最底层是基础水体着色包括深浅颜色混合、菲涅尔边缘光、高光反射这些常规操作。往上一层是流动层用Flow Map驱动两张噪声贴图的UV偏移让水面产生流动的纹理变化。再往上是细节层叠加一层法线贴图的流动来模拟水面的微小波纹。最上面是交互层比如根据深度图做岸边泡沫、根据场景物体做折射扰动。这次重点讲的是流动层和细节层的实现因为这是Flow Map最核心的应用场景也是UV偏移最容易踩坑的地方。基础着色和交互层会简单带过保证整体效果的完整性。2. Flow Map的核心原理与UV偏移避坑指南2.1 Flow Map贴图的制作与导入设置Flow Map的制作方式有好几种。最常用的是在Photoshop或者Substance Designer里手绘方向用颜色表示流向红色通道控制水平方向绿色通道控制垂直方向中间值0.5表示不偏移。比如纯红色(1,0.5,0.5)表示向右流动纯绿色(0.5,1,0.5)表示向上流动黄色(1,1,0.5)表示向右上流动。这种编码方式的好处是直观美术同学画起来容易理解。另一种方式是用工具生成比如FlowMap Painter或者Houdini的Flow Map节点可以根据模型形状或者噪声自动生成方向场。对于河流这种有明确流向的场景手绘加模糊过渡通常效果更好对于湖面这种方向比较随机的水体用噪声生成会更自然。导入Unity的时候有几个关键设置必须注意。首先Texture Type要选Default不要选Normal Map因为Flow Map不是法线贴图。其次sRGB要取消勾选因为Flow Map存储的是方向数据不是颜色数据如果走sRGB空间会导致方向偏移不准确。Wrap Mode选Repeat这样UV超出0到1范围时能正确平铺。Filter Mode根据需求选Bilinear或者Trilinear一般Bilinear就够了。最后记得把压缩格式设成高质量因为方向数据的精度损失会直接导致流动方向偏差。注意很多人会把Flow Map和法线贴图搞混两者都是RGB贴图但用途完全不同。法线贴图存储的是法线方向需要sRGB勾选Flow Map存储的是UV偏移方向必须取消sRGB。这个设置搞错的话流动方向会偏得离谱。2.2 UV偏移的三种常见错误与正确做法UV偏移这块我踩过的坑最多总结下来有三种典型错误。第一种是直接用Time节点乘以速度去加UV。看起来没问题但时间久了UV值会越来越大到了几万甚至几十万的时候浮点精度就不够了水面会出现明显的抖动和跳变。正确的做法是用Time乘以速度之后取小数部分或者用Fract节点把UV限制在0到1之间。更稳妥的方式是用Time节点的小数输出Shader Graph里Time节点有Sine Time、Cosine Time、Time、Delta Time几个输出直接用Time然后Fract一下就行。第二种是Flow Map的RG通道没有做重映射。前面说过Flow Map的RG范围是0到1但实际偏移量需要-1到1。如果直接拿RG去乘速度加UV相当于所有像素都在往正方向偏移只是偏移量不同这样水面会整体往一个方向跑失去Flow Map的意义。正确的做法是用Remap节点把0到1映射到-1到1或者用Multiply加Add节点手动做先乘以2再减1。第三种是两张噪声贴图的UV偏移方向搞反了。为了让流动更自然通常会用两张噪声贴图以不同速度、不同方向流动然后混合。如果两张图的偏移方向完全一致混合出来还是单向流动如果方向相反混合出来会有拉扯感。比较合适的做法是让两张图有30度到60度的方向差速度差在1.5倍到2倍之间这样混合出来的流动既有层次感又不会互相抵消。2.3 时间缩放与流速控制的参数化设计流速控制看起来简单但要做好用需要把参数拆细。我一般会暴露这几个参数基础流速Base Speed、流向强度Flow Strength、噪声缩放Noise Scale、时间缩放Time Scale。基础流速控制整体快慢流向强度控制Flow Map的影响权重噪声缩放控制纹理大小时间缩放用来做全局变速比如慢动作效果。这里有个细节Time Scale不要直接乘在Time上而是乘在流速上。因为Time是全局的如果多个材质共用同一个Time但Time Scale不同直接乘Time会导致每个材质的时间基准不一致动画会错位。正确做法是Time乘以Base Speed再乘以Time Scale这样每个材质独立控制自己的速度互不影响。另外流速的数值范围建议控制在0.01到0.5之间。太小了看不出流动太大了水面会像快进的录像。如果是河流0.05到0.15比较合适如果是海浪0.1到0.3如果是瀑布可以到0.5以上。这些数值不是绝对的要根据场景比例和相机距离来调。2.4 噪声贴图的选择与混合策略噪声贴图的选择直接影响水面的质感。Perlin噪声比较柔和适合湖面或者缓慢流动的河水Worley噪声有细胞感适合模拟水面泡沫或者沸腾效果Simplex噪声介于两者之间通用性最好。Unity的Mathf.PerlinNoise可以在C#里生成噪声但在Shader里一般用预先生成的噪声贴图性能更好。混合策略上我习惯用两层噪声做Lerp然后用第三层噪声做扰动。具体来说第一层噪声用Flow Map驱动UV偏移速度设为1倍第二层噪声也用Flow Map驱动但速度设为1.7倍方向偏转45度两层用Lerp节点按0.5混合。第三层噪声不参与流动只用来扰动前两层的混合权重让混合过渡更自然。混合的时候要注意两层噪声的UV不能完全一样否则混合出来还是同一张图。一般会让第二层噪声的UV缩放跟第一层差一个质数倍比如第一层缩放是3第二层就是3.7或者5.3这样两层纹理不会周期性对齐流动看起来更随机。3. Shader Graph节点搭建全流程3.1 基础水体着色的节点框架打开Shader Graph先建一个URP的Lit Shader Graph或者Unlit也行看项目需求。我一般用Unlit然后自己加光照计算这样控制更灵活。基础着色部分需要这几个节点Base Color、Normal、Emission、Alpha。Base Color用深水和浅水两个颜色做LerpLerp的权重用菲涅尔节点。菲涅尔节点在Shader Graph里叫Fresnel Effect输入Normal Vector和View Direction输出一个0到1的值边缘接近1中间接近0。用这个值做Lerp水面边缘会偏浅色中间偏深色符合真实水体的视觉规律。Normal部分先留空等流动层做完再接进来。Emission用来做水面的高光反射可以用简单的Blinn-Phong模型也可以用环境贴图采样。Alpha根据需求设成1或者根据深度做透明如果是河流一般设1如果是浅水池塘可以设0.8左右。3.2 Flow Map驱动UV偏移的节点连线这是核心部分。从Sample Texture 2D节点开始把Flow Map贴图接进去输出RG通道。然后用Split节点把RG拆开分别做RemapR通道从0到1映射到-1到1G通道同样处理。Remap节点在Shader Graph里叫Remap输入In、In Min Max、Out Min Max设置In Min Max为0和1Out Min Max为-1和1。Remap之后得到的是方向向量用Multiply节点乘以Flow Strength和Base Speed再用Multiply乘以Time。Time节点直接用默认输出不需要Fract因为后面加UV的时候会自动处理。然后把这个偏移量加到噪声贴图的UV上。UV来源是UV节点默认是UV0如果模型有多个UV通道可以选UV1。这里有个优化点如果Flow Map的RG通道在导入时已经做了重映射比如在Photoshop里把0.5设为基准那Shader里就不需要再Remap了。但为了通用性我建议还是在Shader里做Remap这样Flow Map的制作标准统一换图的时候不用改节点。3.3 双层噪声混合与流动细节叠加第一层噪声的UV偏移做完之后复制一套同样的节点做第二层。第二层的区别在于Flow Strength乘以1.7Time乘以1.3方向偏转用Rotate节点旋转45度。Rotate节点在Shader Graph里叫Rotate输入UV和Center输出旋转后的UV。旋转角度用Radians转换一下45度就是0.785弧度。两层噪声采样完之后用Lerp混合权重设0.5。混合结果接到Normal From Texture节点生成法线或者直接接到Normal输入。如果要做更精细的水面可以在两层噪声之外再加一层高频噪声做细节法线UV偏移速度设成第一层的2.5倍混合权重0.3。细节叠加的时候注意法线的强度要控制好。太强了水面像塑料太弱了看不出波纹。一般Normal Strength设在0.3到0.8之间根据噪声贴图的对比度调整。如果噪声贴图本身对比度很高Normal Strength就设小一点反之设大一点。3.4 深度图与岸边泡沫的交互处理岸边泡沫是提升水体真实感的关键细节。原理是用场景深度图减去水面深度得到水面到场景物体的距离距离越小说明越靠近岸边泡沫越明显。Shader Graph里用Scene Depth节点获取场景深度用Screen Position节点的W分量获取水面深度两者相减得到深度差。深度差经过Smoothstep处理映射到0到1的泡沫强度。然后用这个强度去Lerp泡沫颜色和水面颜色。泡沫纹理可以用噪声贴图加阈值也可以用专门的泡沫贴图。泡沫的UV也要跟着水面流动但速度要比水面慢一些这样泡沫看起来是浮在水面上的而不是跟着水跑。注意Scene Depth节点在URP里需要勾选Depth Texture选项在URP Asset的Inspector里找到Depth Texture勾上。不勾的话Scene Depth返回的是0泡沫效果出不来。这个坑我踩过好几次每次换项目都要重新检查一遍。4. 常见问题排查与性能优化实录4.1 水面抖动与UV精度问题的排查水面抖动是最常见的问题表现是水面流动到一定程度后突然跳一下或者整体出现高频抖动。原因基本可以锁定在UV精度上。Unity的UV默认是float2精度足够但如果UV值累积到很大比如Time跑了几小时float精度就不够了。解决方案有两个一是用Fract节点把UV限制在0到1之间但这样会导致纹理接缝处不连续二是用Time节点的小数部分Shader Graph的Time节点有个Fraction输出直接用它就不会累积。我一般用第二种简单可靠。还有一种抖动是Flow Map贴图的压缩导致的。如果Flow Map被压缩成低质量格式方向数据会有量化误差流动到某些区域时方向会突变。解决方法是把Flow Map的压缩格式设成RG Compressed或者高质量在Texture Import Settings里把Compression改成High Quality。4.2 流动方向与预期不符的调试方法流动方向不对通常有三个原因Flow Map的sRGB设置错了、Remap范围搞反了、UV的V轴方向跟预期相反。排查的时候可以先把Flow Map直接接到Base Color上看看颜色分布是否符合预期。如果Flow Map显示出来是偏红的说明R通道值高应该往右流如果显示偏绿应该往上流。如果颜色对了但方向反了检查Remap的Out Min Max是不是设成了1和-1。有些教程会写成-1和1顺序反了方向就反了。另外Unity的UV原点在左下角而有些贴图工具的原点在左上角导入的时候V轴会翻转导致上下方向相反。这种情况在导入设置里把Flip Green Channel勾上就行。4.3 移动端性能优化要点移动端做Flow Map水体性能优化主要抓三个点贴图数量、采样次数、计算复杂度。贴图数量上Flow Map一张、噪声两张、法线一张加起来四张对于中高端移动设备可以接受。如果还要加泡沫贴图和深度图就有点多了可以考虑把泡沫信息打包到噪声贴图的Alpha通道里。采样次数上两层噪声就是两次采样加上Flow Map一次法线一次总共四次。如果性能吃紧可以把两层噪声合并成一张贴图的两个通道用一次采样拿到两层信息但这样两层噪声的UV必须一样流动方向就统一了效果会打折扣。计算复杂度上Remap和Multiply都是廉价操作主要开销在采样和Lerp。如果还开了抗锯齿和HDR移动端压力会比较大。建议在移动端把Normal Strength调低或者干脆关掉法线只保留颜色流动等性能有余量再加回来。4.4 常见问题速查表问题现象可能原因排查方法解决方案水面整体往一个方向跑Flow Map未做Remap检查Remap节点是否连接添加Remap节点0到1映射到-1到1流动方向与预期相反sRGB设置错误或V轴翻转把Flow Map接到Base Color看颜色取消sRGB勾选或勾选Flip Green Channel水面抖动跳变UV精度不足检查Time是否直接乘速度加UV用Time的Fraction输出或Fract节点两层噪声混合后无层次两层UV缩放相同检查两层噪声的Tiling值第二层Tiling设为第一层的质数倍岸边泡沫不显示Depth Texture未开启检查URP Asset的Depth Texture选项勾选Depth Texture移动端帧率骤降采样次数过多用Profiler查看GPU耗时合并噪声贴图通道降低Normal Strength水面边缘有硬边菲涅尔强度过高检查Fresnel Effect的Power值降低Power值或改用Smoothstep过渡流动速度不均匀Flow Map方向数据压缩损失检查Flow Map压缩格式设为高质量压缩或RG Compressed4.5 实操心得与避坑建议第一个心得Flow Map的模糊过渡比精确方向更重要。很多人画Flow Map的时候追求每个像素方向都精确结果流动起来很生硬。实际上Flow Map需要一定的模糊让相邻像素的方向平滑过渡这样水面流动才有连贯性。在Photoshop里画完之后加个高斯模糊半径2到5像素效果会好很多。第二个心得噪声贴图的Tiling不要设整数。整数Tiling会导致纹理在UV边界处周期性对齐流动看起来有重复感。用3.7、5.3、7.1这种质数或者带小数的值能有效打破周期性。这个技巧在做任何平铺纹理的时候都适用。第三个心得Time Scale和Base Speed要分开控制。我见过很多项目把这两个合并成一个参数结果做全局变速的时候所有水体的相对速度都乱了。分开控制的话全局变速只改Time Scale每个水体的相对速度保持不变调整起来灵活得多。第四个心得在Scene视图里用Gizmos画Flow Map的方向。虽然Shader Graph不能直接画Gizmos但可以写个简单的Editor脚本在OnDrawGizmos里根据Flow Map贴图采样方向画箭头。这样调方向的时候直观很多不用反复进Play模式看效果。第五个心得移动端慎用Scene Depth。Scene Depth在移动端需要额外的Depth Prepass开销不小。如果项目对性能要求高可以考虑用顶点颜色或者UV通道来标记岸边区域绕过深度图计算。虽然精度差一点但性能收益很明显。5. 效果扩展与进阶玩法5.1 加入折射与反射提升真实感基础流动做完之后可以加折射和反射让水面更真实。折射用Grab Pass或者URP的Camera Opaque Texture采样屏幕空间纹理然后根据法线做UV扰动。扰动强度跟法线强度挂钩法线越强扰动越大。反射用Reflection Probe或者Planar Reflection前者性能好但精度低后者效果好但开销大根据项目需求选。折射有个细节扰动UV的时候要用屏幕空间的UV不是模型的UV。Screen Position节点输出屏幕UV加上法线乘以扰动强度再去采样Opaque Texture。扰动强度一般设0.01到0.05太大了画面会扭曲得看不清。5.2 交互式水面的实现思路交互式水面比如角色走过水面产生涟漪原理是用一张Render Texture记录交互位置然后在Shader里采样这张RT根据距离计算涟漪强度。具体做法是角色脚下挂一个相机朝下拍或者用Command Buffer把交互位置画到RT上。Shader里用RT的UV去采样得到交互强度叠加到法线或者高度上。这个方案在PC上很成熟移动端要谨慎因为Render Texture的更新和采样都有开销。如果只是简单的脚步涟漪可以用粒子系统代替性能更好但效果没那么精细。5.3 不同水体类型的参数预设河流、湖泊、海洋、瀑布这几种水体的参数差异很大。河流流速快、方向明确Flow Strength设0.8到1.0Base Speed设0.1到0.2噪声Tiling设2到4。湖泊流速慢、方向随机Flow Strength设0.3到0.5Base Speed设0.02到0.05噪声Tiling设1到2。海洋有波浪需要叠加顶点动画或者高度图Flow Map只负责表面细节。瀑布是垂直流动Flow Map的V方向要统一朝下速度设0.3到0.5。我一般会做几个材质预设美术同学直接拖到模型上就能用不用每次从头调参数。预设存在Project里用Material Variant或者Prefab管理换项目的时候直接拷贝过去。5.4 与Crest或其它水体方案的对比Crest是Unity社区里比较成熟的开源水体方案功能很全包括波浪模拟、泡沫、折射、反射、水下效果等等。相比之下Shader Graph手搓Flow Map水体的优势在于轻量和可控。Crest适合做海洋这种大场景功能多但开销也大Flow Map适合做河流、池塘这种小场景性能好、调参直观。如果项目里已经有Crest也可以把Flow Map作为补充用在Crest覆盖不到的小水面上。两者不冲突Crest管大水面Flow Map管小水面视觉风格统一一下就行。5.5 后续可以继续折腾的方向Flow Map水体做完之后还可以往几个方向扩展。一是加水下效果用后处理做水下的颜色偏移和扭曲二是加天气系统下雨的时候水面涟漪变多、流速变快三是加季节变化冬天水面结冰、流动变慢甚至停止。这些扩展都是在现有框架上加节点不用推翻重来。另外可以研究一下用Compute Shader做Flow Map的实时生成比如根据地形高度和重力方向自动计算流向这样美术不用手绘Flow Map程序化生成更高效。不过这个复杂度比较高适合有Compute Shader基础的同学折腾。我个人在实际操作中的体会是Flow Map水体效果的核心不在于节点多复杂而在于参数调得细不细。同样的节点连线流速差0.01、噪声Tiling差0.5效果可能天差地别。所以做完之后不要急着收工在Scene视图里多转转相机从不同角度、不同距离看效果调到一个自己觉得舒服的状态再保存。另外记得把参数暴露成Material Property这样同一个Shader可以复用到不同水体上不用每个水面都复制一份Shader Graph。