Unity风格化水面着色器全攻略:从Shader Graph到性能优化

发布时间:2026/7/12 12:45:52
Unity风格化水面着色器全攻略:从Shader Graph到性能优化 1. 项目概述为什么我们需要风格化水面在Unity里做水面效果大家第一时间想到的可能是那些追求物理真实感的PBR水体波浪、反射、折射、焦散一应俱全。但很多时候尤其是在卡通渲染、低多边形风格或者独立游戏中这种“真实”反而会破坏整体的美术统一性。想象一下一个色彩明快、造型简约的卡通世界里突然出现一片写实到能看清海底礁石纹理的海水是不是感觉特别出戏这就是“风格化水面着色器”存在的意义。它不追求物理正确而是追求视觉正确和风格统一。它的核心目标是用可控、高效且富有表现力的方式模拟出水体的“感觉”而不是其物理本质。比如《塞尔达传说旷野之息》里那片波光粼粼又清澈见底的湖泊或者《原神》中那些色彩梦幻、边界清晰的水域都是风格化水体的典范。我接手过不少项目从休闲手游到风格化Demo几乎都绕不开对水体的定制。很多新手甚至是有经验的开发者一提到写Shader就头疼觉得深不可测。其实拆解开来一个让人眼前一亮的风格化水面无非是几个核心视觉元素的组合基础颜色、波浪动画、边缘泡沫、镜面高光。本指南的目的就是带你一步步用Shader Graph可视化着色器编辑工具或者手写Shader代码把这些元素像搭积木一样组合起来打造出专属于你项目的惊艳卡通水体。无论你是美术想更好地控制效果还是程序想深入理解渲染管线这篇指南都会提供从思路到实现的完整路径。2. 核心视觉元素拆解与设计思路一个成功的风格化水面绝不是一堆复杂技术的堆砌而是有明确的设计目标。在动手写任何一行代码或连接任何一个节点之前我们必须先想清楚我们想要的水看起来是什么样的2.1 基础色与深度渐变营造水体通透感真实的水之所以看起来是蓝色或绿色是因为它对不同波长光线的吸收。在风格化处理中我们简化这个物理过程用基于深度的颜色渐变来模拟。核心思路离水面越近深度值小颜色越浅可能带点泡沫的白色或浅绿色离水面越远深度值大颜色越深过渡到深蓝或墨绿色。这个“深度”在Shader里通常通过场景深度纹理Camera Depth Texture与水面像素的世界空间Y坐标做差来得到。注意在URP通用渲染管线或HDRP高清渲染管线中获取深度纹理需要先在Renderer Asset中勾选相应选项。这是新手最容易卡住的第一步配置不对后续所有基于深度的效果都无法实现。在Shader Graph中你可以通过Scene Depth节点获取当前像素的深度然后与通过Position节点设置为World空间得到的Y值进行计算。用一个Smoothstep或Remap节点来控制深度到颜色的映射关系再连接到一个Lerp线性插值节点在两个颜色之间进行平滑过渡。这里的关键在于控制渐变区域的宽度和过渡的平滑度太生硬会像油漆太模糊则缺乏质感。实操心得不要只用简单的两种颜色做线性渐变。我通常会准备一个至少包含3-4个关键色的渐变条Gradient分别对应极浅水区、浅水区、深水区和非常深的区域。用Sample Gradient节点根据归一化的深度值采样这样得到的水色层次感会丰富得多也更接近手绘的感觉。2.2 波浪动画赋予水面生命力静止的水面是死板的。风格化水面的波浪通常有两种做法基于顶点动画和基于法线贴图扰动。1. 顶点动画直接修改水面网格顶点的Y轴坐标模拟波浪起伏。这种方法性能消耗相对较高顶点数多时影响更大但效果直接适合需要较大幅度波浪的场景。常用函数使用sin、cos函数叠加并赋予不同的频率、振幅和方向可以创造出复杂的波谱。公式类似offset.y sin(vertex.x * frequency time * speed) * amplitude。Shader Graph实现使用Time节点获取时间结合Position节点的XZ坐标输入到Sine或Cosine节点再乘以控制参数最后通过Add节点加到顶点位置的Y分量上。记得要在节点前勾选“Vertex Position”端口。2. 法线贴图扰动这是更主流、性能更好的方式。我们使用一张或两张用于流动混合波浪法线贴图通过UV动画让其在水面流动从而影响光照计算产生波光粼粼的视觉效果而网格本身并不变形。贴图选择风格化水面通常使用特制的、对比度较高、细节概括的法线贴图而不是写实的那种细腻噪波。UV动画用时间和一个速度参数对UV坐标进行偏移Tiling And Offset节点。使用两张法线贴图以不同速度和方向滚动然后混合Blend Normal节点可以避免产生明显的重复图案。影响范围通过深度控制法线强度。浅水区波浪扰动应该更明显深水区则减弱这样更符合视觉规律。个人建议对于移动端或性能敏感的项目优先使用法线贴图扰动。顶点动画留给那些需要表现巨浪或特殊互动如船体划过的局部效果。两者也可以结合使用用微弱的顶点动画做基础起伏再加法线贴图丰富细节。2.3 边缘泡沫与海岸线定义水域边界这是风格化水体的“灵魂笔触”。一条干净利落、带有泡沫的岸边能立刻让水体“立”在场景中而不是漂浮在空中。实现原理边缘泡沫同样高度依赖深度信息。在岸边即深度非常小接近0的区域我们让水面呈现泡沫的颜色通常是白色或米黄色。计算边缘因子用saturate(1 - depth / foamRange)这样的公式。depth是计算出的水深foamRange控制泡沫出现的范围。当深度小于foamRange时因子大于0。添加噪波直接使用平滑的边缘会显得很假。我们需要引入噪波纹理来打破边缘的规则性。用一张噪声图或使用程序化噪声对边缘因子进行扰动。模拟涌浪更高级的效果是让泡沫有一种“涌上来又退下去”的动画。这可以通过将噪声图的UV加上一个随时间变化的偏移来实现并且让偏移量在靠近岸边时更大。在Shader Graph中的节点流先通过深度计算出一个基础的边缘遮罩Mask然后用Noise节点采样并通过Time节点驱动其UV偏移。将噪声与基础遮罩相乘或相加取决于想要的效果最后用一个Power节点来锐化或柔化泡沫的边缘形状。结果用于混合水体基础色和泡沫色。避坑技巧泡沫的颜色不要用纯白色255,255,255会显得很“扎眼”且没有体积感。尝试用220,230,240这样的淡蓝色白色或者245,240,230这样的米白色并赋予一点点非常微弱的蓝色或环境色影响会自然得多。2.4 高光与反射捕捉那一抹亮光风格化水面的高光通常比较“夸张”和“风格化”可能是一个明亮的光斑或者一条闪烁的光带用以指示光源方向和增加视觉趣味。1. 镜面高光Specular使用Blinn-Phong或更简单的计算模型。核心是计算视角方向与反射光方向的点积。在风格化中我们经常对这个结果进行“硬切”处理。操作计算出的高光强度通过一个Power节点控制光斑大小和硬度后再用Step或Smoothstep节点进行阈值化处理得到一个边界清晰的高光区域而不是真实渲染中那种柔和的衰减。动画可以让高光强度随时间发生微弱脉动或者根据波浪法线进行扰动使其有闪烁感。2. 平面反射Planar Reflection对于平静的湖面或水池简单的屏幕空间反射SSR或立方体贴图反射可能不够或者性能开销大。风格化项目常用一种取巧的“假反射”采样一张渲染好的场景纹理可能是上一帧的也可能是一个专门的低分辨率渲染相机捕捉的经过适当的扭曲用法线贴图扰动UV后叠加在水面上。这种方法可控性强性能好虽然物理上不准确但视觉上足以“骗过”眼睛。设计选择如果你的游戏是俯视角或固定视角可以精心调整一张静态的环境贴图作为反射源效果既好又省性能。动态开放世界则需要更动态的方案如使用反射探针Reflection Probe结合简单的UV扰动。3. 在URP中使用Shader Graph完整实现理论说再多不如动手做一遍。下面我们以Unity URP管线为例使用Shader Graph从零搭建一个包含上述所有元素的风格化水面着色器。我假设你已经创建了一个URP项目并安装了Shader Graph包。3.1 创建Shader Graph与基础设置在Project窗口中右键 - Create - Shader - Universal Render Pipeline - Shader Graph。命名为“StylizedWater”。双击打开。首先设置Graph的精度对于移动端可以考虑用Half精度以节省带宽PC端可以用Float。在Graph Inspector面板中设置。关键步骤启用深度纹理。这是很多效果的基础。你需要确保你的URP Asset通常在Settings文件夹中Renderer列表下使用的Renderer Asset如Universal Renderer Data的配置里勾选了Opaque Texture它包含了深度信息。在Shader Graph的Graph Settings中勾选Camera Depth Texture选项。3.2 构建基础网络深度、颜色与泡沫我们先从主颜色开始搭建。获取深度与计算水深添加一个Scene Depth节点。它的默认输出是眼睛空间深度。我们需要将其转换为线性深度或者直接与水面高度比较。更常用的方法是获取像素的世界位置Y值与场景深度对应的世界位置Y值做差。这需要一点计算Scene Depth节点连接一个Linear Eye Depth节点如需然后使用Depth节点提供的Eye深度通过View Direction需要转换为世界空间等节点可以重建世界位置但过程较复杂。更实用的简化方法对于平静的水面网格是平面我们可以用一个Position节点空间设置为World取其Y分量作为水面高度。然后用Scene Depth节点减去这个水面高度就得到了近似的水深。虽然不完全精确但对于风格化效果足够用且性能极佳。公式节点示例Depth SceneDepth - WorldPos.y。将结果通过Saturate或Clamp限制在合理范围。基于深度的颜色渐变创建一个Gradient属性定义你的水色变化。例如从浅青#a8e6cf到中蓝#64b5f6再到深蓝#1a237e。添加一个Sample Gradient节点。将上一步计算出的、经过归一化除以最大可视深度的Depth值连接到Time端口它在这里充当采样位置。将这个颜色输出作为水体的Base Color。添加边缘泡沫创建一个Foam Color属性浅白色和Foam Range属性Float约0.05-0.2。计算泡沫遮罩FoamMask 1 - saturate(Depth / FoamRange)。这样在深度为0时遮罩为1深度大于FoamRange时遮罩为0。添加一个Noise节点如Simple Noise将其UV端口连接上水面世界位置的XZ坐标可能需要乘以一个Tiling参数并加上Time乘以Speed的偏移量。将Noise的输出与FoamMask相乘用于打破边缘。可以用Power节点调整噪声的影响强度。最后用这个处理后的遮罩在Base Color和Foam Color之间进行线性插值Lerp。遮罩为1的地方显示泡沫色为0的地方显示基础水色。3.3 加入波浪法线与高光法线贴图与流动创建两个Texture 2D属性用于两张风格化的法线贴图。再创建两个Vector2属性作为它们的滚动速度如(0.05, 0.03)和(-0.03, 0.04)。分别对两张法线贴图使用Sample Texture 2D节点。在采样前对UV使用Tiling And Offset节点其Offset输入连接Time乘以各自的Speed。使用Blend Normal节点混合两张采样得到的法线。将混合后的法线连接到Master节点的Normal端口。深度控制法线强度在深水区减弱法线扰动。可以创建一个Normal Intensity属性然后与归一化的深度反转1-深度进行相乘或插值再应用到法线向量的强度上。这需要将法线从切线空间转换处理后再转回去或者直接对法线贴图的采样结果RG通道进行缩放。风格化高光添加Specular Color和Specular Power属性。使用Blinn-Phong光照模型需要视角方向和半角向量。Shader Graph提供了Specular节点在PBR Master下是Specular在Unlit Master下需自己计算。更简单的风格化高光直接计算视角方向与法线反射方向的点积。使用Reflection Vector节点和View Direction节点。对点积结果使用Power节点输入Specular Power得到一个锐利的高光核心。使用Step或Smoothstep节点设定一个阈值如0.9将高于阈值部分切出来作为高光区域。将这个高光遮罩与Specular Color相乘然后加到最终的颜色输出上注意是加法混合Add。3.4 整合与主节点配置将上面计算出的Base Color已混合泡沫、Normal、Specular输出分别连接到Master节点通常是PBR Master或Unlit Master的对应端口。使用PBR Master更标准可以接受场景光照。将颜色连接到Base Color法线连接到Normal高光颜色可以连接到Emission来自发光来模拟强烈的风格化高光或者连接到Specular并调整平滑度。使用Unlit Master完全自定义光照更轻量适合完全手绘风格或性能极端受限的情况。你需要自己计算所有颜色叠加。最后的关键设置渲染队列Queue设置为Transparent3000或Geometry1以确保水面在大多数不透明物体之后渲染正确处理透明和深度。混合模式Blend如果需要半透明效果如水下模糊使用SrcAlpha OneMinusSrcAlpha。对于大多数卡通水体表面可以是不透明的Opaque边缘泡沫部分通过颜色混合实现透明感所以也可以保持Opaque通过Alpha Clip来处理边缘。深度写入ZWrite通常设置为On确保深度缓冲正确避免后续物体错误地渲染在水面之上。面剔除Cull设置为Back剔除背面或Off双面渲染如果你能看到水底。完成这些后保存Shader Graph创建一个材质球应用它然后赋给一个平面网格拖到场景里你就能看到初步的效果了。接下来就是漫长的参数微调阶段。4. 性能优化与进阶技巧一个好看但跑不动的Shader是没有意义的。风格化Shader的优势之一就是性能可控。以下是一些优化和提升质量的技巧。4.1 针对移动平台与WebGL的优化策略精度选择在Shader Graph的Graph Settings中将Precision设置为Half。对于颜色和范围在0-1之间的参数Half精度足够且能显著提升移动端和WebGL的性能。纹理压缩与尺寸法线贴图和噪声图尽量使用压缩格式如ASTC、ETC2并控制尺寸512x512或256x256通常足够。避免使用超大纹理。计算简化减少复杂的数学运算如sin/cos。如果必须用考虑使用纹理查找Texture Lookup来模拟简单的周期函数。将可以预先计算的值放在顶点着色器中计算而不是片元着色器。例如波浪的顶点位移。对于深度计算如果水面是平坦的使用前面提到的简化版世界高度差法避免昂贵的重建世界位置计算。减少纹理采样尽可能复用纹理采样。例如用一张纹理的R、G、B通道存储不同的噪声或者用法线贴图的Alpha通道存储高度信息用于泡沫。LOD与剔除对于远处的水面可以使用更简化的Shader变体通过Shader LOD实现或者直接使用一个更简单的平面着色。4.2 提升视觉质量的“黑科技”水下扭曲折射模拟水面下物体看起来的扭曲效果。这可以通过抓取屏幕纹理GrabPass或在URP中使用Scene Color节点然后用法线贴图去扰动采样UV来实现。在Shader Graph中使用Scene Color节点并将其UV输入加上法线向量的XY分量乘以一个强度系数。注意这个效果在移动端开销较大慎用。交互涟漪让角色走入水中或物体落入水中时产生涟漪。这通常需要将交互信息位置、强度通过脚本传递到Shader中。在Shader中计算每个像素到交互点的距离根据距离和衰减函数生成一个波浪位移或法线扰动并随时间淡出。这需要一定的编码能力将数据写入材质属性或全局Shader变量如Shader.SetGlobalVector。焦散效果风格化真实的焦散模拟非常昂贵。风格化项目中可以用一张流动的、高对比度的光斑纹理投影到水底通过世界XZ坐标采样并随着水面法线轻微扰动来模拟这种光影效果。多级细节LOD融合在岸边使用细节丰富的泡沫和波浪在远离镜头的地方逐渐淡出这些细节过渡到平静的、颜色单一的水面。这可以通过相机距离来控制相关参数的强度。4.3 材质参数化与美术控制一个好的风格化水面Shader应该将控制权最大限度地交给美术。这意味着所有重要的视觉参数都应该暴露为材质属性。建议暴露的参数列表参数组参数名类型说明颜色Deep Color/Shallow ColorColor或直接使用一个GradientFoam ColorColor泡沫颜色Specular ColorColor高光颜色波浪Wave Speed 1/2Vector2两张法线贴图的滚动速度Wave Tiling 1/2Float法线贴图平铺度Normal IntensityFloat法线整体强度Wave Height/FrequencyFloat如果用了顶点动画泡沫Foam RangeFloat泡沫出现的水深范围Foam Noise TilingFloat泡沫噪声纹理大小Foam Noise SpeedFloat泡沫噪声流动速度Foam HardnessFloat泡沫边缘的软硬程度Power高光Specular PowerFloat高光集中度Specular ThresholdFloat高光显示阈值将这些参数组织在材质面板上美术就可以像调节Photoshop滑块一样快速调出沙漠绿洲、寒冰湖泊、熔岩河流等不同氛围的水体而无需程序员每次都修改Shader代码。5. 常见问题排查与实战心得即使按照步骤操作你也可能会遇到各种“坑”。这里记录了一些我反复遇到的问题和解决方法。5.1 水面闪烁或深度冲突Z-fighting这是最常见的问题表现为水面与地面或其他物体交界处出现闪烁的像素。原因水面网格与地面网格在深度缓冲中的值过于接近由于浮点数精度限制GPU无法确定谁在前谁在后。解决方案轻微抬高水面在建模时让水面网格比实际视觉位置稍微抬高一点点例如0.001个单位。这是最简单有效的方法。调整渲染队列确保水面在Transparent队列渲染并且所有不透明物体如地面已经渲染完毕。使用深度偏移Depth Offset在Shader Graph的Master节点设置中或手写Shader中使用Offset指令。给水面一个微小的深度偏移强制它“浮”在其他物体之上。但需谨慎使用过度偏移会导致渲染顺序问题。5.2 边缘泡沫在特定角度消失或异常原因深度计算依赖于从相机到水底的交点。当视线与水面夹角非常小掠射角时深度纹理采样可能失败或得到错误值。解决方案钳制深度值对计算出的深度进行最大最小值钳制Clamp避免出现极端负值或超大正值。使用线性眼深度确保使用的Scene Depth是线性化的而非原始的硬件深度。在Shader Graph中Scene Depth节点默认输出非线性深度可以连接Linear Eye Depth节点转换。添加视角衰减计算视角方向与水面上法线的点积。当夹角过小时逐步减弱甚至关闭泡沫效果。虽然物理上不正确但能避免视觉瑕疵。5.3 性能热点分析与定位如果游戏运行时帧率下降怀疑是水面Shader导致的使用Unity Profiler在GPU模块下查看最耗时的渲染函数。如果看到你的Shader名称排在前面并且每个实例的耗时很高就需要优化。检查Draw Call一个巨大的水面网格可能只是一个Draw Call但如果水面被分割成很多小块或者使用了大量重叠的透明效果可能会增加Draw Call。简化片元着色器片元着色器Fragment Shader的计算量是性能的关键。回顾你的Shader Graph网络检查是否有特别复杂的节点组合尤其是循环、大量条件判断或高频率的噪声计算。尝试简化或寻找近似替代方案。减少纹理采样如前所述合并纹理。使用Texture2D Array或Texture Atlas来减少纹理切换。5.4 与后期处理Post Processing的配合风格化水面经常需要和屏幕空间后效配合比如泛光Bloom来强化高光或者颜色分级Color Grading来统一色调。泛光Bloom水面的高光区域特别是风格化处理后的硬边高光是触发泛光的理想来源。确保你的高光输出到了正确的通道如Emission并且在URP的Bloom后处理设置中阈值Threshold设置得当能捕捉到这些高光。环境光遮蔽SSAO屏幕空间环境光遮蔽可能会在水面与岸边交接处产生不正确的暗边。如果遇到此问题可以考虑让水面Shader不参与SSAO计算这通常需要修改或使用特定的Shader变体。动态分辨率与FSR/DLSS在适配这些技术时要确保你的Shader中所有基于屏幕坐标如用于折射的Screen Position或屏幕纹理采样的计算能正确应对渲染目标分辨率的变化。通常使用ComputeScreenPos等函数来处理会比直接使用UV更稳健。打造一个令人满意的风格化水面是一个不断迭代和调参的过程。没有一劳永逸的参数最好的效果总是诞生于对项目视觉风格的深刻理解以及美术与程序之间的紧密配合。从最基础的深度色开始逐步叠加波浪、泡沫、高光每次只调整一两个参数观察变化你就能逐渐掌控这片“数字水域”的每一个细节。记住最终目标是服务于整体画面而不是炫技。当你的水面能够完美地融入世界不再引人注目却又不可或缺时它就真正成功了。