1. 项目概述为什么选择ShaderGraph做动态水面动态水面效果几乎是所有3D游戏和交互应用的“氛围感”担当。无论是开放世界的湖泊还是RPG里的魔法池一个灵动的水面能瞬间提升场景的真实感和沉浸感。过去要实现这种效果你得和复杂的Shader代码打交道各种数学公式、顶点/片元着色器编程门槛不低。但现在Unity的ShaderGraph彻底改变了游戏规则。ShaderGraph是一个基于节点的可视化着色器编辑工具。你可以把它想象成一个乐高积木台各种图形化的“节点”就是积木块。通过拖拽和连线这些节点你就能直观地“组装”出复杂的着色器效果完全不用写一行代码。这对于美术、技术美术甚至是对Shader感兴趣的程序员来说简直是生产力神器。这次我们要做的“5分钟动态水面”核心目标就是利用ShaderGraph快速实现一个包含基础颜色、动态波纹、菲涅尔效应边缘透亮和法线扰动的完整水面材质。这个效果不仅看起来专业而且性能可控非常适合移动端和PC端的实时渲染。下面我们就从零开始一步步拆解这个效果的实现逻辑和每一个关键节点。2. 核心思路与节点设计蓝图在动手连接节点之前我们必须先想清楚一个动态水面Shader需要哪些核心组成部分。盲目连线只会得到一堆混乱的节点理清思路才能高效实现。2.1 效果拆解动态水面的四大支柱一个看起来真实的动态水面通常由以下几个视觉层叠加而成基础色与深度色水不是单一颜色。靠近岸边或浅水区颜色较浅如淡蓝绿色深水区颜色较深如深蓝色。这需要根据水面深度来混合两种颜色。动态波纹波浪这是“动态”二字的灵魂。水面需要有随时间变化的、起伏的波纹。我们将通过噪声纹理Noise Texture和正弦波Sine函数来模拟。菲涅尔效应这是一个关键的物理光学现象。当视线与水面法线夹角越大即看向水面边缘时反射越弱透射越强水面看起来就越透明能看到水下的内容当视线垂直看向水面时反射最强能看到更多天空或环境的倒影。这个效应能极大地增强水面的体积感和真实感。法线扰动仅有颜色和波纹起伏还不够。我们需要扰动水面的法线信息这样当光线照射到水面时才会产生动态的、破碎的高光反射让水面看起来波光粼粼。2.2 节点方案选型为什么是这些节点基于以上拆解我们的ShaderGraph节点方案就清晰了深度计算使用“场景深度”节点获取摄像机到水下物体的距离与一个“平面”节点代表水面高度做减法得到每个像素点的水深。用这个水深值去混合两种颜色。波纹生成核心是“噪声纹理”节点。我们选择“Simple Noise”或“Gradient Noise”因为它们能产生连续、自然的随机图案。为了让噪声动起来我们需要将噪声纹理的UV坐标与“时间”节点相加这样噪声图案就会随时间平移形成波纹移动的假象。为了增加波纹的层次感我们通常会使用两套不同缩放和速度的噪声进行叠加。菲涅尔效应ShaderGraph提供了现成的“菲涅尔效应”节点。它的原理是计算视线方向与表面法线的点积。我们需要将“摄像机方向”和“表面法线”输入该节点输出一个从边缘值接近1到中心值接近0的渐变遮罩。用这个遮罩去混合水面颜色和深度颜色或直接控制透明度。法线扰动我们同样使用动态的噪声纹理。但这次我们将噪声输出一个单通道或灰度值输入到一个“法线Normal”节点或者直接与现有的水面法线纹理进行混合从而改变法线方向影响光照计算。提示在ShaderGraph中所有计算本质上都是在处理数据流。颜色是Vector3RGBUV坐标是Vector2时间是个Float。理解每个节点输入输出的数据类型是正确连线的关键。3. 完整节点配置与分步详解现在我们进入ShaderGraph编辑器开始实际的节点搭建。请跟随以下步骤并理解每一步的意图。3.1 创建Graph与基础设置首先在Project窗口右键 - Create - Shader - Blank Shader Graph。我建议命名为“Water_Simple”。双击打开。在Graph设置Graph Inspector中确保“Graph Type”为“Sub Graph”或“Master”取决于Unity版本新版本通常为Sub Graph。在“Active Targets”中添加需要的渲染管线支持如Universal RP。这一步确保了我们的Shader能在正确的渲染环境下工作。3.2 构建动态波纹系统这是最核心的部分我们一步步来。创建基础UV与时间流动拉出一个“Time”节点它输出一个随时间线性增长的值。拉出一个“UV”节点选择“TexCoord”通道这代表模型本身的UV坐标。拉出一个“Add”节点。将“Time”节点的“Time”输出口连接到“Add”节点的“B”端口将“UV”节点的输出连接到“A”端口。这样我们就得到了一个随时间不断“滑动”的UV坐标。注意直接加时间会导致UV滑动太快通常我们会用一个“Multiply”节点将时间乘以一个较小的系数如0.1来控制波纹移动速度。创建一个“Float”属性命名为“_FlowSpeed”默认值0.1然后用它乘以Time后再与UV相加。生成第一层噪声波纹拉出一个“Simple Noise”节点。将上一步得到的“流动UV”连接到它的“UV”输入口。调整Simple Noise的“Scale”参数。这个值控制噪声的“密度”或“频率”。值越大波纹越细碎值越小波纹越平缓。我建议创建一个“Float”属性“_NoiseScale1”来控制它初始值可以设为10。Simple Noise节点的输出是一个介于0到1之间的灰度值。这个值就代表了第一层波纹的高度信息。生成第二层噪声波纹增加细节复制步骤1和2但使用另一套UV和参数。具体操作再创建一组“UV”和“Time”计算但这次在将Time与UV相加前先将UV用一个“Tiling And Offset”节点进行缩放Scale让第二层噪声的UV缩放与第一层不同例如Scale设为(1.5, 1.5)。再拉出一个“Simple Noise”节点使用这第二套UV并设置不同的“Scale”例如通过属性“_NoiseScale2”设为20。将两个Simple Noise节点的输出用一个“Add”节点相加。这样两层不同频率的噪声叠加在一起波纹的细节会更加丰富自然避免了单一噪声带来的单调感。将噪声转换为顶点偏移上一步得到的噪声值需要影响水面的形状。拉出一个“Multiply”节点将相加后的噪声与一个“Float”属性“_WaveHeight”相乘这个属性控制波纹的振幅高度。拉出一个“Position”节点设置于Object Space。拉出一个“Add”节点。将“Position”节点连接到“A”将乘以高度后的噪声值需要先通过一个“Combine”节点转换为Vector3通常只影响Y轴连接到“B”。这样我们就根据噪声值在物体的Y轴向上方向上偏移了每个顶点的位置。关键一步将这个“Add”节点的输出连接到Master Stack或Master Node的“Vertex Position”输入端口。至此动态起伏的水面网格就完成了。3.3 实现深度颜色与菲涅尔混合水面颜色要随深度变化并且边缘要透亮。计算水面深度拉出一个“Scene Depth”节点。这个节点需要深度纹理Depth Texture的支持。在URP中你需要确保摄像机的“Opaque Texture”选项打开或在Graph设置中启用Depth Texture。拉出一个“Plane”节点或使用Position节点在World Space下的Y分量这代表我们设定的水面世界坐标高度。拉出一个“Subtract”节点。用“Scene Depth”减去“Plane”的Y值得到的就是水深。如果结果为负说明该点在水平面以上应视为水深为0。这里可以用一个“Max”节点将相减结果与0取最大值。基于深度混合颜色创建两个“Color”属性“_ShallowColor”浅水色如#87CEEB和“_DeepColor”深水色如#1E90FF。拉出一个“Lerp”线性插值节点。将“_ShallowColor”连接到“A”“_DeepColor”连接到“B”。将上一步计算出的“水深”值连接到“T”插值因子。同时我们需要用一个“Saturate”节点或者“Clamp”节点将水深值限制在0-1的范围内或者用一个“Power”节点对水深进行非线性处理让颜色过渡更自然。例如lerp(浅色 深色 saturate(水深 * 0.5))这里的0.5是一个可调节的“深度强度”系数可以做成属性“_DepthFactor”。添加菲涅尔效应拉出一个“Fresnel Effect”节点。将“View Direction”节点摄像机方向和“Normal”节点表面法线分别接入。调整“Power”参数。这个值控制菲涅尔效应的过渡范围。值越大边缘透亮区域越窄值越小过渡越平缓。通常设置在3到8之间。可以创建一个属性“_FresnelPower”来调节。“Fresnel Effect”节点的输出是一个从中心0到边缘1的渐变值。最终颜色合成现在我们有三个关键元素基于深度的颜色Base Color、菲涅尔遮罩Fresnel Mask、以及可能还需要一个基础纹理颜色如果有贴图的话。通常的合成方式是用菲涅尔遮罩去影响透明度或颜色的混合。例如我们可以再创建一个“Color”属性“_FresnelColor”通常是白色或淡蓝色用于模拟边缘高光或透射光。使用另一个“Lerp”节点。将“Base Color”连接到“A”将“_FresnelColor”或直接用白色连接到“B”将“Fresnel Mask”连接到“T”。这样水面边缘就会混合进更多的菲涅尔颜色显得更透亮。最后将这个“Lerp”的输出连接到Master Stack的“Base Color”端口。3.4 创建动态法线与高光为了让水面有波光粼粼的反光我们必须扰动法线。生成法线扰动图我们可以复用之前为波纹创建的动态噪声。但更专业的做法是使用一张专门的法线贴图Normal Map然后用动态的UV去采样它这样得到的法线信息更准确。创建一个“Texture 2D”属性导入一张水面的法线贴图。将我们之前制作的“流动UV”连接到这张法线贴图的“UV”输入。拉出一个“Normal From Texture”节点在某些版本中法线贴图节点直接有法线输出。将法线贴图的RGB输出连接到此节点它就会将贴图颜色转换为法线向量。混合顶点法线与扰动法线拉出一个“Blend Normals”节点。将“Normal”节点原始的顶点法线连接到“Base”将“Normal From Texture”的输出连接到“Blend”。这个节点的作用是平滑地混合两种法线避免生硬的过渡。将“Blend Normals”的输出连接到Master Stack的“Normal”端口。配置高光反射在Master Stack中找到“Specular”相关输入。对于URP的Lit Shader通常有“Smoothness”光滑度和“Specular Color”高光颜色或“Metallic”金属度。创建一个“Float”属性“_Smoothness”控制水面高光区域的大小和锐利度值在0粗糙到1光滑之间水面通常设为0.8-0.95。高光颜色通常保持默认的白色即可。如果你希望水面有金属感可以适当提高“Metallic”值但一般水体设为0。将“_Smoothness”属性连接到Master Stack的“Smoothness”端口。4. 参数调试心得与性能优化要点节点连好了但效果可能还不尽人意。这时就需要细致的调试。同时作为实时渲染效果性能至关重要。4.1 关键参数调试指南以下属性是你需要重点调节的它们共同决定了水面的最终观感属性名建议初始值调节效果注意事项_FlowSpeed0.1控制波纹移动速度。值越大水流越快。避免过快会显得不自然。可与游戏内时间系统关联。_NoiseScale1/210, 20控制两层噪声的波纹密度。值大则波纹细碎值小则波浪平缓。两层尺度最好有差异如1:2以增加细节层次。_WaveHeight0.05控制波纹起伏的振幅高度。单位通常是米。值太大会导致水面穿帮或模型 clipping根据场景比例调整。_DepthFactor0.5控制深度颜色过渡的强度。值越大浅色区域越小。结合场景实际深度调整最好在Scene视图中实时调节。_FresnelPower5.0控制菲涅尔效应的范围。值越大边缘透亮区域越窄。这是提升真实感的关键参数多尝试不同角度观察。_Smoothness0.9控制水面高光的锐利度。值越高反光越清晰、区域越小。过高可能产生刺眼的高光点适当即可。调试技巧不要一次性调节所有参数。建议的调试顺序是先调_WaveHeight和_NoiseScale让波纹形状满意然后调_FlowSpeed让动态节奏合适接着调_DepthFactor和颜色让水下深浅感出来最后精细调节_FresnelPower和_Smoothness提升质感和反射效果。务必在场景的不同位置和不同摄像机角度下观察。4.2 性能优化与常见问题排查一个好看的Shader如果太耗性能也是不合格的。以下是几个优化点和常见坑位纹理与计算开销纹理采样我们使用了噪声纹理或法线贴图。确保这些纹理的尺寸合理如512x512并启用Mipmaps。如果使用程序化噪声Simple Noise节点它本身是计算出来的没有纹理采样开销但计算量会随精度提高而增加。节点简化检查Graph中是否有冗余的计算。例如多处使用的相同计算如流动UV可以提取成一个“Sub Graph”或通过一个节点输出给多处使用避免重复计算。精度选择在ShaderGraph中可以选择计算精度Float / Half。对于颜色、UV等数据Half精度通常足够且速度更快。可以在节点属性中更改。常见问题与解决方案问题水面边缘有锯齿或闪烁Z-fighting。原因水面网格与地面或水底模型完全共面或极度接近深度缓冲精度不足导致。解决将水面网格在模型编辑器中稍微向上提升一个微小距离如0.001单位。或者在Shader中对顶点位置施加一个极其微小的偏移但注意不要影响波纹。问题Scene Depth节点采样失败深度色不显示。原因渲染管线没有生成深度纹理或者摄像机没有启用深度渲染。解决在URP Asset的Renderer设置中确保“Depth Texture”选项是勾选的。同时检查使用该材质的摄像机其“Render Type”是否设置为“Opaque”透明物体可能无法正确写入深度。问题波纹移动不自然有跳跃感。原因可能是Time节点直接与UV相加没有乘以速度系数导致UV变化过快每帧偏移过大。也可能是噪声纹理的Wrap Mode不是“Repeat”导致在边界处跳变。解决确保使用了UV (Time * _FlowSpeed)的公式。如果使用程序化噪声检查其“Type”是否为“Gradient”或“Simple”这两种类型通常是无缝的。问题在移动设备上帧率下降明显。原因Shader计算过于复杂或者波纹计算全在片段着色器中进行。解决尽可能将计算移到顶点着色器Vertex Shader。在我们的配置中波纹高度计算已经连到了“Vertex Position”这很好。检查是否还有复杂的计算如多次噪声采样、复杂的颜色计算在片段着色器路径上考虑是否可以简化或使用更低精度的计算。进阶优化思路LOD细节层次为水面Shader制作简化版本用于远距离渲染。例如远处水面可以去掉第二层噪声、降低菲涅尔计算精度甚至使用静态法线贴图。Tessellation曲面细分如果你的目标平台支持如PC可以考虑使用曲面细分来动态增加水面网格的顶点数从而获得更平滑、更细致的波纹。但这属于进阶内容会显著增加GPU负载。使用Custom Function节点对于极其特定的、性能敏感的计算如果ShaderGraph原生节点效率不高可以编写一小段HLSL代码封装到“Custom Function”节点中进行高度优化。5. 从效果到应用场景集成与扩展思路Shader制作完成最终要放到场景里才能发挥价值。这里分享一些集成经验和让效果更出彩的扩展想法。5.1 场景集成与材质设置创建一个平面Plane作为水面将我们做好的Shader拖到其上生成材质。调整材质的属性参数使其与场景光照和环境匹配。网格细分默认的Plane只有10x10的网格顶点数太少无法表现细腻的波纹。你需要增加其网格密度。在3D建模软件中细分或者在Unity中通过导入高模也可以使用ProBuilder等工具现场细分。一个200x200顶点的水面通常能获得不错的效果。光照与环境反射确保场景中有合理的天空盒Skybox或反射探针Reflection Probe。水面的高光和菲涅尔反射非常依赖环境信息。一个HDR天空盒能让水面反光更加真实。后期处理可以结合屏幕空间反射SSR或平面反射Planar Reflection来让水面反射场景中的动态物体。虽然性能开销较大但对品质提升是质的飞跃。URP中可以通过额外的Renderer Feature来实现平面反射。5.2 效果扩展与创意玩法基础动态水面只是一个起点你可以在此基础上玩出很多花样交互式涟漪这是让水面“活”起来的终极技巧。实现思路是当角色走入水中或物体落入水中时在世界空间对应位置生成一个“涟漪信息”如位置、强度、时间通过脚本传递到Shader使用Shader全局属性如Shader.SetGlobalVector。在Shader中根据当前像素与世界涟漪中心的距离和经过的时间计算一个衰减的波纹高度叠加到原有的噪声波纹上。泡沫与浪花在水面与岸边或物体交界处可以采样一张泡沫贴图。利用深度值或通过计算水面网格与岸边物体的距离可使用SDF距离场或纹理来生成一个遮罩用这个遮罩来显示泡沫纹理并让泡沫纹理也随着水流UV移动。水下效果当摄像机潜入水下时整个后处理效果应该发生变化。这通常通过检测摄像机高度是否低于水面高度来实现。可以触发一个全屏的后处理Volume添加颜色偏移偏蓝绿、模糊、焦散Caustics投影等效果。焦散效果可以通过在Shader中投影一张动态的、模拟光线透过水面形成的明暗纹理到水底来实现。流动贴图除了程序化噪声你还可以使用一张“流动贴图”Flow Map来控制水流的方向和速度。这比单纯的UV平移更能模拟复杂的河道、漩涡效果。你需要一张RGBA纹理其中RG通道存储方向向量B通道存储速度。在Shader中采样这张图用其向量来扭曲噪声采样的UV。最后一点个人体会ShaderGraph最大的优势是迭代速度快所见即所得。不要害怕尝试和连接不同的节点即使连接错了也不会崩溃。多利用“Blackboard”来管理属性保持Graph的整洁。把一个复杂效果拆解成多个“Sub Graph”子图比如把“波纹生成”、“颜色计算”、“法线计算”分别做成子图会让主Graph非常清晰也便于复用。动态水面的美感在于“动态”和“交互”当你的水面能对游戏世界的事件做出反应时玩家的沉浸感会成倍增加。从这“5分钟”的基础效果出发去探索更广阔的可能性吧。
ShaderGraph实战:5分钟打造Unity动态水面效果
1. 项目概述为什么选择ShaderGraph做动态水面动态水面效果几乎是所有3D游戏和交互应用的“氛围感”担当。无论是开放世界的湖泊还是RPG里的魔法池一个灵动的水面能瞬间提升场景的真实感和沉浸感。过去要实现这种效果你得和复杂的Shader代码打交道各种数学公式、顶点/片元着色器编程门槛不低。但现在Unity的ShaderGraph彻底改变了游戏规则。ShaderGraph是一个基于节点的可视化着色器编辑工具。你可以把它想象成一个乐高积木台各种图形化的“节点”就是积木块。通过拖拽和连线这些节点你就能直观地“组装”出复杂的着色器效果完全不用写一行代码。这对于美术、技术美术甚至是对Shader感兴趣的程序员来说简直是生产力神器。这次我们要做的“5分钟动态水面”核心目标就是利用ShaderGraph快速实现一个包含基础颜色、动态波纹、菲涅尔效应边缘透亮和法线扰动的完整水面材质。这个效果不仅看起来专业而且性能可控非常适合移动端和PC端的实时渲染。下面我们就从零开始一步步拆解这个效果的实现逻辑和每一个关键节点。2. 核心思路与节点设计蓝图在动手连接节点之前我们必须先想清楚一个动态水面Shader需要哪些核心组成部分。盲目连线只会得到一堆混乱的节点理清思路才能高效实现。2.1 效果拆解动态水面的四大支柱一个看起来真实的动态水面通常由以下几个视觉层叠加而成基础色与深度色水不是单一颜色。靠近岸边或浅水区颜色较浅如淡蓝绿色深水区颜色较深如深蓝色。这需要根据水面深度来混合两种颜色。动态波纹波浪这是“动态”二字的灵魂。水面需要有随时间变化的、起伏的波纹。我们将通过噪声纹理Noise Texture和正弦波Sine函数来模拟。菲涅尔效应这是一个关键的物理光学现象。当视线与水面法线夹角越大即看向水面边缘时反射越弱透射越强水面看起来就越透明能看到水下的内容当视线垂直看向水面时反射最强能看到更多天空或环境的倒影。这个效应能极大地增强水面的体积感和真实感。法线扰动仅有颜色和波纹起伏还不够。我们需要扰动水面的法线信息这样当光线照射到水面时才会产生动态的、破碎的高光反射让水面看起来波光粼粼。2.2 节点方案选型为什么是这些节点基于以上拆解我们的ShaderGraph节点方案就清晰了深度计算使用“场景深度”节点获取摄像机到水下物体的距离与一个“平面”节点代表水面高度做减法得到每个像素点的水深。用这个水深值去混合两种颜色。波纹生成核心是“噪声纹理”节点。我们选择“Simple Noise”或“Gradient Noise”因为它们能产生连续、自然的随机图案。为了让噪声动起来我们需要将噪声纹理的UV坐标与“时间”节点相加这样噪声图案就会随时间平移形成波纹移动的假象。为了增加波纹的层次感我们通常会使用两套不同缩放和速度的噪声进行叠加。菲涅尔效应ShaderGraph提供了现成的“菲涅尔效应”节点。它的原理是计算视线方向与表面法线的点积。我们需要将“摄像机方向”和“表面法线”输入该节点输出一个从边缘值接近1到中心值接近0的渐变遮罩。用这个遮罩去混合水面颜色和深度颜色或直接控制透明度。法线扰动我们同样使用动态的噪声纹理。但这次我们将噪声输出一个单通道或灰度值输入到一个“法线Normal”节点或者直接与现有的水面法线纹理进行混合从而改变法线方向影响光照计算。提示在ShaderGraph中所有计算本质上都是在处理数据流。颜色是Vector3RGBUV坐标是Vector2时间是个Float。理解每个节点输入输出的数据类型是正确连线的关键。3. 完整节点配置与分步详解现在我们进入ShaderGraph编辑器开始实际的节点搭建。请跟随以下步骤并理解每一步的意图。3.1 创建Graph与基础设置首先在Project窗口右键 - Create - Shader - Blank Shader Graph。我建议命名为“Water_Simple”。双击打开。在Graph设置Graph Inspector中确保“Graph Type”为“Sub Graph”或“Master”取决于Unity版本新版本通常为Sub Graph。在“Active Targets”中添加需要的渲染管线支持如Universal RP。这一步确保了我们的Shader能在正确的渲染环境下工作。3.2 构建动态波纹系统这是最核心的部分我们一步步来。创建基础UV与时间流动拉出一个“Time”节点它输出一个随时间线性增长的值。拉出一个“UV”节点选择“TexCoord”通道这代表模型本身的UV坐标。拉出一个“Add”节点。将“Time”节点的“Time”输出口连接到“Add”节点的“B”端口将“UV”节点的输出连接到“A”端口。这样我们就得到了一个随时间不断“滑动”的UV坐标。注意直接加时间会导致UV滑动太快通常我们会用一个“Multiply”节点将时间乘以一个较小的系数如0.1来控制波纹移动速度。创建一个“Float”属性命名为“_FlowSpeed”默认值0.1然后用它乘以Time后再与UV相加。生成第一层噪声波纹拉出一个“Simple Noise”节点。将上一步得到的“流动UV”连接到它的“UV”输入口。调整Simple Noise的“Scale”参数。这个值控制噪声的“密度”或“频率”。值越大波纹越细碎值越小波纹越平缓。我建议创建一个“Float”属性“_NoiseScale1”来控制它初始值可以设为10。Simple Noise节点的输出是一个介于0到1之间的灰度值。这个值就代表了第一层波纹的高度信息。生成第二层噪声波纹增加细节复制步骤1和2但使用另一套UV和参数。具体操作再创建一组“UV”和“Time”计算但这次在将Time与UV相加前先将UV用一个“Tiling And Offset”节点进行缩放Scale让第二层噪声的UV缩放与第一层不同例如Scale设为(1.5, 1.5)。再拉出一个“Simple Noise”节点使用这第二套UV并设置不同的“Scale”例如通过属性“_NoiseScale2”设为20。将两个Simple Noise节点的输出用一个“Add”节点相加。这样两层不同频率的噪声叠加在一起波纹的细节会更加丰富自然避免了单一噪声带来的单调感。将噪声转换为顶点偏移上一步得到的噪声值需要影响水面的形状。拉出一个“Multiply”节点将相加后的噪声与一个“Float”属性“_WaveHeight”相乘这个属性控制波纹的振幅高度。拉出一个“Position”节点设置于Object Space。拉出一个“Add”节点。将“Position”节点连接到“A”将乘以高度后的噪声值需要先通过一个“Combine”节点转换为Vector3通常只影响Y轴连接到“B”。这样我们就根据噪声值在物体的Y轴向上方向上偏移了每个顶点的位置。关键一步将这个“Add”节点的输出连接到Master Stack或Master Node的“Vertex Position”输入端口。至此动态起伏的水面网格就完成了。3.3 实现深度颜色与菲涅尔混合水面颜色要随深度变化并且边缘要透亮。计算水面深度拉出一个“Scene Depth”节点。这个节点需要深度纹理Depth Texture的支持。在URP中你需要确保摄像机的“Opaque Texture”选项打开或在Graph设置中启用Depth Texture。拉出一个“Plane”节点或使用Position节点在World Space下的Y分量这代表我们设定的水面世界坐标高度。拉出一个“Subtract”节点。用“Scene Depth”减去“Plane”的Y值得到的就是水深。如果结果为负说明该点在水平面以上应视为水深为0。这里可以用一个“Max”节点将相减结果与0取最大值。基于深度混合颜色创建两个“Color”属性“_ShallowColor”浅水色如#87CEEB和“_DeepColor”深水色如#1E90FF。拉出一个“Lerp”线性插值节点。将“_ShallowColor”连接到“A”“_DeepColor”连接到“B”。将上一步计算出的“水深”值连接到“T”插值因子。同时我们需要用一个“Saturate”节点或者“Clamp”节点将水深值限制在0-1的范围内或者用一个“Power”节点对水深进行非线性处理让颜色过渡更自然。例如lerp(浅色 深色 saturate(水深 * 0.5))这里的0.5是一个可调节的“深度强度”系数可以做成属性“_DepthFactor”。添加菲涅尔效应拉出一个“Fresnel Effect”节点。将“View Direction”节点摄像机方向和“Normal”节点表面法线分别接入。调整“Power”参数。这个值控制菲涅尔效应的过渡范围。值越大边缘透亮区域越窄值越小过渡越平缓。通常设置在3到8之间。可以创建一个属性“_FresnelPower”来调节。“Fresnel Effect”节点的输出是一个从中心0到边缘1的渐变值。最终颜色合成现在我们有三个关键元素基于深度的颜色Base Color、菲涅尔遮罩Fresnel Mask、以及可能还需要一个基础纹理颜色如果有贴图的话。通常的合成方式是用菲涅尔遮罩去影响透明度或颜色的混合。例如我们可以再创建一个“Color”属性“_FresnelColor”通常是白色或淡蓝色用于模拟边缘高光或透射光。使用另一个“Lerp”节点。将“Base Color”连接到“A”将“_FresnelColor”或直接用白色连接到“B”将“Fresnel Mask”连接到“T”。这样水面边缘就会混合进更多的菲涅尔颜色显得更透亮。最后将这个“Lerp”的输出连接到Master Stack的“Base Color”端口。3.4 创建动态法线与高光为了让水面有波光粼粼的反光我们必须扰动法线。生成法线扰动图我们可以复用之前为波纹创建的动态噪声。但更专业的做法是使用一张专门的法线贴图Normal Map然后用动态的UV去采样它这样得到的法线信息更准确。创建一个“Texture 2D”属性导入一张水面的法线贴图。将我们之前制作的“流动UV”连接到这张法线贴图的“UV”输入。拉出一个“Normal From Texture”节点在某些版本中法线贴图节点直接有法线输出。将法线贴图的RGB输出连接到此节点它就会将贴图颜色转换为法线向量。混合顶点法线与扰动法线拉出一个“Blend Normals”节点。将“Normal”节点原始的顶点法线连接到“Base”将“Normal From Texture”的输出连接到“Blend”。这个节点的作用是平滑地混合两种法线避免生硬的过渡。将“Blend Normals”的输出连接到Master Stack的“Normal”端口。配置高光反射在Master Stack中找到“Specular”相关输入。对于URP的Lit Shader通常有“Smoothness”光滑度和“Specular Color”高光颜色或“Metallic”金属度。创建一个“Float”属性“_Smoothness”控制水面高光区域的大小和锐利度值在0粗糙到1光滑之间水面通常设为0.8-0.95。高光颜色通常保持默认的白色即可。如果你希望水面有金属感可以适当提高“Metallic”值但一般水体设为0。将“_Smoothness”属性连接到Master Stack的“Smoothness”端口。4. 参数调试心得与性能优化要点节点连好了但效果可能还不尽人意。这时就需要细致的调试。同时作为实时渲染效果性能至关重要。4.1 关键参数调试指南以下属性是你需要重点调节的它们共同决定了水面的最终观感属性名建议初始值调节效果注意事项_FlowSpeed0.1控制波纹移动速度。值越大水流越快。避免过快会显得不自然。可与游戏内时间系统关联。_NoiseScale1/210, 20控制两层噪声的波纹密度。值大则波纹细碎值小则波浪平缓。两层尺度最好有差异如1:2以增加细节层次。_WaveHeight0.05控制波纹起伏的振幅高度。单位通常是米。值太大会导致水面穿帮或模型 clipping根据场景比例调整。_DepthFactor0.5控制深度颜色过渡的强度。值越大浅色区域越小。结合场景实际深度调整最好在Scene视图中实时调节。_FresnelPower5.0控制菲涅尔效应的范围。值越大边缘透亮区域越窄。这是提升真实感的关键参数多尝试不同角度观察。_Smoothness0.9控制水面高光的锐利度。值越高反光越清晰、区域越小。过高可能产生刺眼的高光点适当即可。调试技巧不要一次性调节所有参数。建议的调试顺序是先调_WaveHeight和_NoiseScale让波纹形状满意然后调_FlowSpeed让动态节奏合适接着调_DepthFactor和颜色让水下深浅感出来最后精细调节_FresnelPower和_Smoothness提升质感和反射效果。务必在场景的不同位置和不同摄像机角度下观察。4.2 性能优化与常见问题排查一个好看的Shader如果太耗性能也是不合格的。以下是几个优化点和常见坑位纹理与计算开销纹理采样我们使用了噪声纹理或法线贴图。确保这些纹理的尺寸合理如512x512并启用Mipmaps。如果使用程序化噪声Simple Noise节点它本身是计算出来的没有纹理采样开销但计算量会随精度提高而增加。节点简化检查Graph中是否有冗余的计算。例如多处使用的相同计算如流动UV可以提取成一个“Sub Graph”或通过一个节点输出给多处使用避免重复计算。精度选择在ShaderGraph中可以选择计算精度Float / Half。对于颜色、UV等数据Half精度通常足够且速度更快。可以在节点属性中更改。常见问题与解决方案问题水面边缘有锯齿或闪烁Z-fighting。原因水面网格与地面或水底模型完全共面或极度接近深度缓冲精度不足导致。解决将水面网格在模型编辑器中稍微向上提升一个微小距离如0.001单位。或者在Shader中对顶点位置施加一个极其微小的偏移但注意不要影响波纹。问题Scene Depth节点采样失败深度色不显示。原因渲染管线没有生成深度纹理或者摄像机没有启用深度渲染。解决在URP Asset的Renderer设置中确保“Depth Texture”选项是勾选的。同时检查使用该材质的摄像机其“Render Type”是否设置为“Opaque”透明物体可能无法正确写入深度。问题波纹移动不自然有跳跃感。原因可能是Time节点直接与UV相加没有乘以速度系数导致UV变化过快每帧偏移过大。也可能是噪声纹理的Wrap Mode不是“Repeat”导致在边界处跳变。解决确保使用了UV (Time * _FlowSpeed)的公式。如果使用程序化噪声检查其“Type”是否为“Gradient”或“Simple”这两种类型通常是无缝的。问题在移动设备上帧率下降明显。原因Shader计算过于复杂或者波纹计算全在片段着色器中进行。解决尽可能将计算移到顶点着色器Vertex Shader。在我们的配置中波纹高度计算已经连到了“Vertex Position”这很好。检查是否还有复杂的计算如多次噪声采样、复杂的颜色计算在片段着色器路径上考虑是否可以简化或使用更低精度的计算。进阶优化思路LOD细节层次为水面Shader制作简化版本用于远距离渲染。例如远处水面可以去掉第二层噪声、降低菲涅尔计算精度甚至使用静态法线贴图。Tessellation曲面细分如果你的目标平台支持如PC可以考虑使用曲面细分来动态增加水面网格的顶点数从而获得更平滑、更细致的波纹。但这属于进阶内容会显著增加GPU负载。使用Custom Function节点对于极其特定的、性能敏感的计算如果ShaderGraph原生节点效率不高可以编写一小段HLSL代码封装到“Custom Function”节点中进行高度优化。5. 从效果到应用场景集成与扩展思路Shader制作完成最终要放到场景里才能发挥价值。这里分享一些集成经验和让效果更出彩的扩展想法。5.1 场景集成与材质设置创建一个平面Plane作为水面将我们做好的Shader拖到其上生成材质。调整材质的属性参数使其与场景光照和环境匹配。网格细分默认的Plane只有10x10的网格顶点数太少无法表现细腻的波纹。你需要增加其网格密度。在3D建模软件中细分或者在Unity中通过导入高模也可以使用ProBuilder等工具现场细分。一个200x200顶点的水面通常能获得不错的效果。光照与环境反射确保场景中有合理的天空盒Skybox或反射探针Reflection Probe。水面的高光和菲涅尔反射非常依赖环境信息。一个HDR天空盒能让水面反光更加真实。后期处理可以结合屏幕空间反射SSR或平面反射Planar Reflection来让水面反射场景中的动态物体。虽然性能开销较大但对品质提升是质的飞跃。URP中可以通过额外的Renderer Feature来实现平面反射。5.2 效果扩展与创意玩法基础动态水面只是一个起点你可以在此基础上玩出很多花样交互式涟漪这是让水面“活”起来的终极技巧。实现思路是当角色走入水中或物体落入水中时在世界空间对应位置生成一个“涟漪信息”如位置、强度、时间通过脚本传递到Shader使用Shader全局属性如Shader.SetGlobalVector。在Shader中根据当前像素与世界涟漪中心的距离和经过的时间计算一个衰减的波纹高度叠加到原有的噪声波纹上。泡沫与浪花在水面与岸边或物体交界处可以采样一张泡沫贴图。利用深度值或通过计算水面网格与岸边物体的距离可使用SDF距离场或纹理来生成一个遮罩用这个遮罩来显示泡沫纹理并让泡沫纹理也随着水流UV移动。水下效果当摄像机潜入水下时整个后处理效果应该发生变化。这通常通过检测摄像机高度是否低于水面高度来实现。可以触发一个全屏的后处理Volume添加颜色偏移偏蓝绿、模糊、焦散Caustics投影等效果。焦散效果可以通过在Shader中投影一张动态的、模拟光线透过水面形成的明暗纹理到水底来实现。流动贴图除了程序化噪声你还可以使用一张“流动贴图”Flow Map来控制水流的方向和速度。这比单纯的UV平移更能模拟复杂的河道、漩涡效果。你需要一张RGBA纹理其中RG通道存储方向向量B通道存储速度。在Shader中采样这张图用其向量来扭曲噪声采样的UV。最后一点个人体会ShaderGraph最大的优势是迭代速度快所见即所得。不要害怕尝试和连接不同的节点即使连接错了也不会崩溃。多利用“Blackboard”来管理属性保持Graph的整洁。把一个复杂效果拆解成多个“Sub Graph”子图比如把“波纹生成”、“颜色计算”、“法线计算”分别做成子图会让主Graph非常清晰也便于复用。动态水面的美感在于“动态”和“交互”当你的水面能对游戏世界的事件做出反应时玩家的沉浸感会成倍增加。从这“5分钟”的基础效果出发去探索更广阔的可能性吧。