Unity ShaderGraph 动态海水效果:从UV动画到顶点起伏的完整实现 📅 2026/8/10 7:42:03 1. 项目概述从静态到灵动的海洋在Unity中制作一个看起来“活”的海面是很多开发者尤其是独立游戏和VR/AR项目开发者绕不开的挑战。你或许试过用复杂的脚本控制顶点位移或者直接导入一个现成的海洋插件但前者性能开销大、效果难调后者则可能过于臃肿与你的项目美术风格格格不入。今天我想分享一个更直接、更可控的方案使用ShaderGraph来打造一个基于UV动画的动态海水效果。这个方案的核心优势在于它完全运行在GPU上性能开销极低却能实现非常细腻、流畅的视觉变化。你不需要写一行Shader代码通过节点式的可视化编辑就能直观地构建出海水表面的波动、色彩变化和光影交互。无论是用于手机游戏的远景海面还是PC端高画质项目的近景水体这套方法都能通过调整参数灵活适配。接下来我会带你从零开始拆解每一个核心节点背后的原理并分享我在实际项目中调试这类效果时积累的“避坑”经验。2. 核心思路拆解如何让一张贴图“动”起来2.1 UV动画的本质时间与空间的游戏要理解动态海水首先要明白UV动画是什么。简单来说模型的UV坐标决定了贴图如何“包裹”在模型表面。UV动画就是让这些坐标随着时间发生变化从而让贴图看起来在移动、流动或旋转。对于海水效果我们通常需要两种基本的运动大尺度平流模拟海面整体的流动方向比如海浪从远处涌向岸边。小尺度细节扰动模拟水面上的细碎波纹、泡沫和光影闪烁增加真实感。在ShaderGraph中我们通过一个关键节点来实现这一切Time节点。它会输出从游戏开始到现在经过的时间以秒为单位。将这个时间值乘以一个控制速度的系数再叠加到原始的UV坐标上UV坐标就会持续偏移贴图也就“动”起来了。2.2 分层与混合构建视觉深度单一层的UV动画会显得非常假像一张平移的塑料布。真实的海面是复杂的它由不同尺度、不同速度、不同方向的波纹叠加而成。因此我们的核心策略是分层。通常至少需要两层基础层Base Layer使用一张包含中低频波纹信息的法线贴图或灰度噪波图。这一层移动较慢决定了海水大体的形态和流动方向。细节层Detail Layer使用另一张高频细节的法线贴图。这一层移动较快叠加在基础层之上用于模拟阳光下的波光粼粼和近处的细碎水波。将这两层信息通常是它们的法线向量按照一定的权重混合就能得到一个既有宏观流向又有微观细节的丰富海面法线信息。这个混合后的法线将直接用于后续的光照计算产生动态的高光和阴影。3. 环境准备与ShaderGraph创建3.1 项目设置与渲染管线适配在开始连接节点之前确保你的Unity项目设置正确。ShaderGraph与Unity的渲染管线Rendering Pipeline紧密相关。确认渲染管线目前主流选择是URPUniversal Render Pipeline或HDRPHigh Definition Render Pipeline。对于大多数移动端和PC端项目URP是平衡性能与效果的最佳选择。你需要在Package Manager中安装并配置好URP。创建ShaderGraph在Project窗口中右键 - Create - Shader - Universal Render Pipeline - Lit Shader Graph如果追求更高自由度也可选Unlit Shader Graph。命名为“OceanWater_ShaderGraph”。材质与测试平面创建新材质将上一步的ShaderGraph赋给它。然后在场景中创建一个Quad或Plane作为海面将材质赋予它。建议将Plane的尺寸设置得大一些如100x100并增加网格细分在导入设置中提高Mesh的平滑度这样波浪变形会更平滑。注意如果你从Asset Store下载的模型或场景无法显示自定义Shader很可能是项目渲染管线不匹配。确保所有材质的Shader都是兼容当前管线如URP的版本。3.2 ShaderGraph编辑器布局初识双击打开创建的ShaderGraph文件你会看到节点编辑界面。主要区域包括主节点图Master Stack位于右侧或底部定义了Shader的最终输出如表面颜色Base Color、法线Normal、光滑度Smoothness等。这是我们最终连接所有计算结果的地方。Blackboard黑板位于左侧用于定义和管理整个Shader的属性Properties。这些属性会暴露在材质面板上方便我们在运行时或编辑器里动态调整参数比如海水颜色、波浪速度等。节点图区域中间最大的区域用于拖拽和连接各种功能节点。我们的工作流通常是先在Blackboard上定义好需要的属性如_Speed,_Tiling然后将这些属性拖入节点图区域作为可用的输入节点再结合其他功能节点进行逻辑运算最后将结果输出到Master Stack的对应端口。4. 构建动态UV系统让海面流动起来4.1 基础UV偏移的实现这是所有动态效果的起点。我们首先要让一张贴图在模型表面流动起来。创建属性在Blackboard上创建两个Vector2类型属性分别命名为_BaseTiling基础贴图平铺和_BaseSpeed基础层速度。将它们拖到节点图区域。获取时间与计算偏移搜索并添加Time节点。将Time节点的Time输出端口连接到一个Multiply乘法节点的A端口将_BaseSpeed属性节点连接到B端口。这个乘法结果就是随时间累积的偏移量。应用偏移到UV搜索并添加Tiling And Offset节点。将默认的UV输入连接到它的UV端口将_BaseTiling属性连接到Tiling端口将上一步计算出的偏移量连接到Offset端口。采样贴图现在将Tiling And Offset节点的输出连接到你准备好的基础层法线贴图或噪波图的Texture 2D Asset节点的UV输入端口。这样贴图的采样坐标就会随着时间不断偏移形成动画。此时如果你将材质赋给平面并运行游戏应该能看到贴图在缓慢移动。但这只是简单的平移缺乏立体感。4.2 多层UV动画的叠加技巧单一方向的流动很枯燥。我们需要通过多层、不同方向的叠加来模拟复杂海面。创建第二层重复4.1的步骤但使用另一张贴图细节层并创建独立的_DetailTiling和_DetailSpeed属性。关键是让_DetailSpeed的值比_BaseSpeed大并且偏移方向_DetailSpeed的X, Y值可以与基础层不同甚至相反。法线贴图解码与混合如果你使用的是法线贴图推荐效果更好从Sample Texture 2D节点输出的RGBA颜色需要被转换为法线向量。使用Normal From Texture节点即可完成转换。分别对基础层和细节层进行转换得到两个法线向量。混合法线添加一个Blend Normal节点。将基础层法线连接到Base细节层法线连接到Blend。这个节点会以正确的物理方式混合两个法线避免能量损失。你还可以创建一个_DetailIntensityFloat类型属性在混合前用Multiply节点调整细节层法线的强度从而控制细节的明显程度。连接主输出将Blend Normal节点的输出连接到Master Stack的Normal输入端口。现在你的海面应该已经有了立体的、动态的波纹细节。实操心得在调整速度(_Speed)和平铺(_Tiling)时不要只盯着数值。_Tiling值越大贴图重复次数越多波纹就越密集、尺度越小。通常基础层的_Tiling较小如1-5细节层的_Tiling较大如10-30。速度方面基础层在0.05-0.1左右细节层在0.1-0.3左右能获得比较自然的效果。多尝试不同的组合。5. 色彩与光照赋予海水生命感5.1 菲涅尔效应模拟水岸交界与深度菲涅尔效应Fresnel Effect描述了这样一个现象视线与表面法线夹角越大即掠射角观看表面的反射越强。对于水面这意味着当你垂直向下看时能看到水下的颜色折射而当你平视远处海面时看到更多的是天空的反射。在ShaderGraph中实现它非常简单添加一个Fresnel Effect节点。将之前混合好的法线向量连接到它的Normal输入确保光照计算正确。调整Power参数。这个值控制菲涅尔效应的过渡范围值越小边缘反射范围越大。对于海水一个中等偏小的值如2-5比较合适。Fresnel Effect节点的输出是一个从0到1的遮罩0代表法线方向水下1代表边缘方向水面反射。5.2 双色混合与深度模拟利用菲涅尔因子我们可以混合两种颜色来模拟海水的深浅变化。创建颜色属性在Blackboard创建_ShallowColor浅水色通常偏绿或青和_DeepColor深水色通常偏蓝或深蓝两个Color属性。线性插值Lerp添加一个Lerp节点。将_ShallowColor连接到A_DeepColor连接到B将Fresnel Effect节点的输出连接到T。这样在视线垂直处T值小显示浅水色在视线掠射处T值大显示深水色。连接颜色输出将Lerp节点的输出连接到Master Stack的Base Color端口。现在你的海水已经具备了基本的色彩变化。但还不够真实的海水是半透明的。5.3 透明与折射的近似实现在URP Lit Shader中我们可以通过调整主节点的Alpha和Transmission等参数来实现水的通透感。透明度在Master Stack上找到Surface Options将Surface Type改为TransparentBlend Mode改为Alpha。然后你可以用一个固定的值如0.8或基于菲涅尔因子调整的值连接到Alpha输入来控制整体透明度。高光与光滑度水是光滑的。将一个较高的固定值如0.9连接到Smoothness输入。高光颜色Specular可以留白或连接一个淡蓝色模拟水的高光。环境光遮蔽AO对于水这种光滑表面通常不需要很强的AO可以断开或给一个很低的值。注意事项开启透明混合Alpha Blend对性能有一定影响且排序问题透明物体渲染顺序可能导致视觉错误。如果海水是场景中的主要大面积透明物体且不需要看到水下复杂模型有时使用不透明Opaque但带有菲涅尔颜色过渡的Shader性能更优效果也足够好。这是一个重要的性能与效果权衡点。6. 顶点动画让海面真正起伏到目前为止我们的海水只有贴图在动模型本身还是平的。要做出波浪起伏的感觉需要操作顶点位置这就是顶点着色器的工作。6.1 基于高度图的顶点位移原理是使用一张灰度图高度图亮的地方代表波峰暗的地方代表波谷。用动态UV采样这张图得到的高度值用来在顶点法线方向上进行偏移。准备高度图可以使用一张噪波图或者用之前的基础层法线贴图的某个通道如G通道来近似。在Blackboard新增一个_WaveHeight波浪高度属性。采样高度使用已经具备动态UV的坐标比如基础层的UV去采样高度图得到一个0-1的值。重映射与偏移通过Remap节点将0-1的值映射到-0.5 * _WaveHeight到0.5 * _WaveHeight的范围。这样中心是0两侧是正负高度。应用位移添加一个Position节点设置空间为Object再添加一个Normal Vector节点同样设置为Object空间。将重映射后的高度值与Normal Vector相乘再与Position相加。最后将结果连接到一个Set Position节点的Position输入并将Set Position节点的输出连接到Master Stack。现在运行游戏你应该能看到海平面有了真实的起伏。调整_WaveHeight可以控制波浪的剧烈程度。6.2 优化性能避免不必要的计算顶点动画非常消耗性能因为每个顶点每帧都要计算。为了优化使用低精度在ShaderGraph的Graph Settings里将Precision设置为Half这能在移动设备上获得更好的性能。简化网格用于海面的网格不必过于复杂。在能接受波浪精度的前提下使用尽可能少的顶点。分块与LOD对于超大海面考虑将其分成多个块Chunks并根据摄像机距离设置不同的网格精度LOD和Shader复杂度。远处的海块可以禁用顶点动画只用UV动画。7. 特效增强泡沫与焦散7.1 动态泡沫边缘模拟泡沫通常出现在波峰顶点位移高的地方以及海岸线。我们可以基于顶点高度和深度来模拟。波峰泡沫在计算顶点位移后我们可以获取位移的绝对值或平方值来代表“起伏强度”。通过一个Remap和Saturate钳制到0-1节点将其转换为一个泡沫遮罩。将这个遮罩与一个泡沫纹理一张黑底白点的贴图进行采样混合然后将结果以Additive叠加的方式混合到最终颜色或自发光Emission上。海岸线泡沫这需要场景中有表示海岸深度的信息通常通过一张单独的海岸深度图或者通过脚本计算顶点世界坐标的Y值与某个“海平面”高度的差值来模拟。在Shader中用这个深度差来驱动一个泡沫纹理的显示越浅的地方泡沫越明显。7.2 简易焦散光效焦散是光线通过水面折射后在海底形成的光斑。一个廉价的实现方法是使用一张焦散光斑的动态纹理通常是一序列图或一张有流动效果的图。用一套独立的、较慢的UV动画系统来采样它。根据水面以上的视角方向或简单的菲涅尔因子来衰减其强度越垂直看水下焦散越可见。将采样结果以Additive方式叠加到海底场景的颜色上这通常需要额外的渲染通道或与场景深度配合实现较复杂。一个更简单的屏幕空间替代方案是将焦散纹理以投影方式叠加在接近水面的物体上但这超出了基础ShaderGraph的范围。8. 完整节点图整合与参数优化8.1 节点图结构化布局当节点越来越多时合理的布局至关重要否则会变成一团乱麻难以调试。功能分组用Sticky Note便签将节点按功能分组如“UV动画层1”、“UV动画层2”、“法线混合”、“颜色与菲涅尔”、“顶点位移”、“泡沫特效”。对齐与分布多使用编辑器提供的对齐工具让连线清晰。子图Sub Graph封装对于重复使用的功能如一套完整的“UV动画层”包含Tiling、Speed、采样、法线转换可以将其创建为Sub Graph。这样主图会非常简洁只需调用几个子图节点。这对于团队协作和Shader复用价值巨大。8.2 关键参数调试心法所有属性创建好后在编辑器里微调是效果成败的关键。这里分享我的调试顺序和常用值范围先调平铺(Tiling)和速度(Speed)确定海浪的尺度感和流动节奏。基础层Tiling(2-5),Speed(0.05, 0.02)细节层Tiling(15-30),Speed(0.1, -0.05)。注意正负号控制方向。再调波浪高度(WaveHeight)从一个小值开始如0.05逐渐增加直到起伏感明显但不过分夸张。对于开阔海洋0.1-0.3可能合适对于池塘0.01-0.05足矣。接着调颜色在场景光照下调整_ShallowColor和_DeepColor。浅水色受海底影响深水色更接近天空色。菲涅尔的Power值在此阶段微调影响颜色过渡区域。最后调光滑度(Smoothness)和泡沫将光滑度调到0.85以上获得湿润反光感。泡沫强度从0开始加一点点增加直到在波峰处有若隐若现的白边即可切忌过量。9. 常见问题与性能排查实录9.1 效果类问题问题1海水看起来像一块平移的塑料布没有立体感。排查首先检查法线贴图是否被正确采样和解码。确保Sample Texture 2D节点后接了Normal From Texture节点并且混合后的法线正确连接到了Master Stack的Normal端口。其次检查光照环境场景中必须有方向光或其他光源法线效果才能显现。解决确保使用真正的法线贴图通常是蓝紫色调的图而不是灰度图。在Sample Texture 2D节点的Type下拉菜单中将其设置为Normal。问题2海水边缘有尖锐的接缝或重复图案很明显。排查这是贴图平铺(Tiling)导致的。UV坐标在边界处跳变。解决使用专门为平铺设计的无缝纹理。如果纹理本身不是无缝的可以在Photoshop中使用“偏移滤镜”并处理中间接缝。此外可以尝试将两层UV动画的平铺比例设为无理数比例如1:1.618让重复图案更不易察觉。问题3顶点波浪起伏不自然有锯齿或网格感。排查网格分辨率太低或者高度图变化太剧烈。解决提高海面网格的细分程度。同时对采样到的高度值使用Smoothstep节点或进行简单的模糊处理例如采样高度图时额外采样周围几个点做平均可以让波浪过渡更平滑。9.2 性能与渲染问题问题4在移动设备上帧率下降严重。排查顶点动画和透明渲染是主要开销。使用Unity的Profiler工具查看GPU耗时最高的部分。解决将Shader的Precision设为Half。减少海面网格顶点数。考虑将Surface Type改回Opaque用菲涅尔模拟透明感关闭真正的透明度混合。对于远处海面使用简化的Shader变体不含顶点动画。问题5透明海水渲染顺序错乱后面的物体透过海水显示不正常。排查这是透明渲染的经典问题。Unity默认按物体到摄像机的距离排序但海面是一个大平面排序可能出错。解决尝试将海面材质Render Queue调至Transparent3000并手动调整。更可靠的方法是将海面拆分成多个部分避免一个巨大的单面片。如果水下内容固定可以考虑使用屏幕后处理的水下效果而非半透明水面。问题6构建后如WebGL效果丢失或变紫。排查最常见原因是贴图导入设置不正确或者Shader变体没有被正确打包。解决检查所有使用的贴图在Import Settings中确保Texture Type正确法线贴图需设为Normal map并且勾选了Generate Mip Maps。在ShaderGraph的Graph Inspector中查看Active Targets确保包含了你的目标平台如Universal WindowsAndroid。如果使用URP确保在Project Settings - Graphics中你的ShaderGraph被包含在URP Asset的Shader列表里。对于WebGL有时需要检查Player Settings中关于着色器精度的设置。调试Shader是一个需要耐心和观察力的过程。多利用ShaderGraph的Preview窗口它可以实时显示任何一个中间节点的计算结果这是定位问题最强大的工具。从最初的UV坐标开始一步步向后检查确保每一环的数据都符合你的预期。