1. 项目概述:从“调参”到“造浪”的思维跃迁
每次看到《绝地求生》里那片随着时间缓缓起伏、波光粼粼的海面,你是不是也和我一样,第一反应是去网上找个现成的Shader,然后一头扎进材质面板里,对着十几个滑块疯狂调试,试图“蒙”出那个感觉?我曾经也这么干过,结果往往是调了半天,水面要么像果冻一样僵硬,要么像沸腾的开水一样诡异,离“以假乱真”差了十万八千里。后来我意识到,问题不在于参数本身,而在于我们根本不知道这些参数背后连接的是什么,它们如何相互作用,最终驱动了那片虚拟的海洋。
这就是今天我想和你聊的核心:别再只会当个“参数调试员”了。我们要用Unity的ShaderGraph,从最底层的原理出发,亲手“搭建”出那片海。这个过程,本质上是在用节点“编程”水的行为逻辑。我将为你拆解实现“吃鸡”级海面效果的五个最关键的ShaderGraph节点,它们就像搭建乐高积木的五个核心零件。掌握了它们,你不仅能复刻出这个效果,更能举一反三,创造出任何你想象中的水面——无论是宁静的湖泊、湍急的河流,还是风暴中的怒涛。这篇文章适合所有对Shader感兴趣,但又被代码吓退的Unity开发者,我们将完全在可视化界面中完成这场造浪之旅。
2. 核心思路拆解:如何用节点“模拟”一片海
在动手连接节点之前,我们必须先想清楚,一片看起来“真实”的海面,在Shader里究竟是由哪些视觉元素构成的。这决定了我们整个节点网络的架构方向。
2.1 视觉元素分解:海面不止是蓝色
首先,我们得抛弃“一个颜色贴图走天下”的简单思维。一片生动的海面是多种视觉信息叠加的结果:
- 基础颜色与深度:近处的海水清澈透亮,颜色较浅;远处的海水深邃,呈现出更浓郁的蓝色甚至墨绿色。这不仅仅是颜色变化,还包含了光线在水体中散射和吸收的物理现象。
- 波浪起伏(顶点动画):海面不是平的,它由无数大小、方向、速度各异的波浪叠加而成。这是让海面“活”起来的第一步,需要通过动态地改变模型顶点的位置来实现。
- 表面细节(法线):即使是一个平滑的波浪曲面,在微观上也是粗糙不平的,有细碎的波纹、涟漪和泡沫。这些细节决定了光线如何反射,是产生“波光粼粼”质感的关键,通常由法线贴图来表现。
- 高光与反射:阳光或月光照射在海面上,会形成明亮的高光点(Specular)和周围环境的反射(Reflection)。高光的形状、强度和锐利度,直接传达了海面的“粗糙度”或“风速”信息。
- 菲涅尔效应:这是一个关键的物理现象——视线与表面法线夹角越小(即掠射角观察水面),反射越强;夹角越大(垂直向下看),越容易看到水下的折射内容。这解释了为什么我们总是看到海天交界处有一条亮线,而脚边的海水则相对透明。
2.2 节点化实现路径
基于以上分解,我们的ShaderGraph构建路径就清晰了:
- 顶点动画层:使用Time和Position节点驱动数学运算,生成波浪的位移信息。
- 表面细节层:使用Texture 2D Asset和Tiling & Offset节点,采样并动态滚动法线贴图,模拟细碎波纹。
- 视觉融合层:使用Normal Blend等节点,将多层细节(如基础波浪和细节涟漪)平滑地混合在一起。
- 光学响应层:使用Fresnel Effect节点和PBR Master节点的输入(如Smoothness),来模拟海面对光线的复杂反应。
整个思路就是从“驱动顶点运动”开始,逐步叠加“表面细节”,最后赋予其“正确的光学属性”。下面,我们就深入这五个决定成败的关键节点。
3. 五大关键节点深度解析与实战连接
这五个节点是构建海面Shader的骨架,理解它们每一个的作用和连接逻辑,比记住一百个参数组合更有用。
3.1 发动机:Time节点——让一切动起来
为什么是它?静态的海面是死海。所有动态效果——波浪的起伏、波纹的流动、光斑的移动——都依赖于一个持续变化的时间信号。Time节点就是ShaderGraph里的时钟,它输出一个随着游戏时间不断增大的浮点数(默认是自场景加载以来的秒数)。
关键操作与原理:仅仅输出时间值还不够,我们需要用它来驱动周期性的运动,比如正弦波。这里就会引入一个经典组合:Time->Multiply(乘以一个控制速度的系数)->Sine(正弦函数)。
- Multiply的作用:乘以一个系数(比如0.5),本质是控制频率。系数越大,正弦波振荡越快,波浪就显得更“急促”或“细密”。
- Sine的作用:将线性增长的时间,转换为在[-1, 1]之间平滑、周期性振荡的值。这个振荡值可以直接用来偏移顶点的Y坐标,形成基础的波浪形状。
实操心得:直接使用
Time节点的输出,变化会非常快。我通常会先连接一个Multiply节点,乘以一个0.1到1之间的小数(例如0.3),得到一个“慢速时间”,再用这个慢速时间去驱动主要波浪,这样动画会更自然,易于控制。对于需要不同速度的次级波纹,则可以使用另一个乘以不同系数(如0.7)的“快速时间”分支。
3.2 空间坐标读取器:Position节点——知道“我在哪”
为什么是它?波浪不可能在所有位置都一样高。我们需要根据顶点在世界空间或物体空间中的水平位置(X和Z坐标)来计算其应有的高度(Y坐标)偏移。Position节点提供了顶点的位置信息。
关键操作与原理:默认的Position节点输出是一个三维向量 (X, Y, Z)。对于海面,我们通常关心的是水平面,即X和Z分量。
- 拆分与重组:使用
Split节点将Position的XYZ分开,取出X和Z。然后,我们可能会用经过Time驱动的数学函数(如Sine)去处理X和Z,得到两个方向的偏移量,再用Combine节点将新的Y值(原始Y+偏移量)与X、Z重新组合成一个新的三维向量。 - 空间选择:
Position节点有World、Object、View等空间选项。对于海面这种可能移动的物体(比如一艘船上的海面),使用Object空间位置来计算波浪,可以保证波浪形状跟随物体移动,更合理。如果是无限大的远景海面,使用World空间则更简单。
一个基础波浪的节点链示例:
Position (Object Space) -> Split -> (取出X) \ Time -> Multiply(Speed) -> Add -> Sine -> Multiply(Amplitude) -> Combine (作为新的Y) / Position (Object Space) -> Split -> (取出Z) -> Multiply(WaveDirection) -> Add这个流程的意思是:用Time经过速度缩放后,加上由位置X和Z按一定方向混合的值,作为正弦函数的输入,从而得到一个随时间和空间位置变化的波浪高度。
3.3 纹理动画控制器:Tiling And Offset节点——制造流动的细节
为什么是它?单靠顶点动画生成的波浪往往过于规则和光滑,缺乏真实海面那种复杂的、多尺度的细节。这时就需要法线贴图。但一张静态的法线贴图贴上去还是死的,我们需要让贴图上的波纹“流动”起来,模拟风驱动力作用下水面的运动。Tiling And Offset节点的Offset输入端口,正是实现纹理滚动的关键。
关键操作与原理:这个节点有两个主要输入:Tiling(缩放)和Offset(偏移)。
Tiling:控制纹理的重复度。值越大,同一区域内重复的纹理次数越多,意味着波纹越细密。对于海面,我们可能需要两层不同尺度的法线贴图,一层Tiling较大模拟大的波浪走向,一层Tiling很大模拟细碎的涟漪。Offset:这是一个二维向量输入,代表纹理在UV空间内的偏移量。核心技巧来了:我们将一个由Time驱动的二维向量(例如(Time * Speed, 0))连接到Offset,纹理就会沿着U方向(水平)持续滚动。如果向量是(Time * Speed1, Time * Speed2),纹理就会沿对角线方向滚动。
实现双层流动细节:为了打破单调性,几乎所有的海面Shader都会使用至少两层法线贴图。
- 第一层(主波纹):使用一张方向性较强的法线贴图,
Tiling较小(如0.05),Offset速度较慢,模拟主要波浪的走向和光影。 - 第二层(细节涟漪):使用另一张或同一张法线贴图,
Tiling较大(如0.15),Offset速度较快且方向与第一层略有不同(例如一个向左,一个向右上)。这能叠加出非常丰富的细节,避免出现明显的重复图案。
3.4 细节混合器:Normal Strength 与 Normal Blend节点——合成真实质感
为什么是它们?当我们有了两层(或更多)滚动的法线贴图后,不能简单地把它们叠加在一起,那样会导致信息混乱。我们需要有控制地混合它们。
Normal Strength节点:这个节点经常被忽略,但它至关重要。它允许我们在混合前,单独调整某一张法线贴图的“强度”或“影响力”。
- 输入:一个法线向量(来自Sample Texture 2D),一个强度系数(Float)。
- 作用:当强度系数为1时,输出原法线;大于1时,增强法线细节(使凹凸感更强);在0到1之间时,减弱细节;为0时,相当于一个平坦的蓝色法线((0,0,1))。
- 实战应用:我们可以为“主波纹”和“细节涟漪”分别设置不同的
Normal Strength。比如,让主波纹强度为0.8,细节涟漪强度为0.4。这样,细节层就不会喧宾夺主,而是在主波纹的基础上增加一些微妙的扰动。
Normal Blend节点:这是将多张法线贴图安全、正确混合的标准节点。它采用一种特殊的算法,能避免直接加法混合导致的向量长度变化(即法线信息失真)。
- 操作:它有两个输入口,
Base(基础法线)和Blend(混合法线)。我们将调整好强度的主波纹法线连入Base,将细节涟漪法线连入Blend。输出就是一个融合了两者信息的、新的法线向量。 - 进阶技巧:你可以进行多次混合。例如,
Normal Blend的输出可以作为新的Base,再与第三层泡沫或特殊效果的法线进行第二次Normal Blend。
避坑指南:千万不要用普通的
Add或Lerp节点去混合法线!法线是归一化向量(长度为1),直接相加会破坏其长度,导致光照计算错误,出现奇怪的黑斑或过度明亮区域。Normal Blend节点是为此场景量身定做的。
3.5 光学特性塑造器:Fresnel Effect节点——模拟水岸交界
为什么是它?这是实现“海水为什么越远越亮,越近越透”这一视觉特性的物理钥匙。菲涅尔效应描述了反射率随观察角度变化的规律。
关键操作与原理:在ShaderGraph中,Fresnel Effect节点根据表面法线(Normal)和观察方向(View Direction)计算出一个渐变系数。这个系数在视线与表面平行时(掠射角)接近1,在垂直时接近0。
- 连接:将我们混合好的最终法线向量连接到
Normal输入口,View Direction通常使用默认的摄像机视图方向。 - 输出应用:这个0到1之间的系数,有两个最主要的用途:
- 控制颜色混合:创建一个
Lerp(线性插值)节点。将深水颜色连到A,浅水或高光颜色连到B,将Fresnel的输出连到T。这样,在视线掠射的海平面远处,将显示更多的B(亮色),在垂直视角的近处,显示更多的A(深色)。 - 控制反射/高光强度:可以将
Fresnel输出连接到PBR Master节点的Smoothness或Environment Reflections的强度上,让边缘的反射更强。
- 控制颜色混合:创建一个
参数调整: 节点上的Power参数控制着菲涅尔效应的过渡锐利度。Power值越小,过渡越平缓、范围越大;值越大,过渡越尖锐,亮暗分界线越明显。对于海面,一个中等偏大的Power值(如3-5)可以模拟出比较清晰的水天线效果。
4. 完整节点网络搭建与参数调试实录
理解了核心节点后,让我们把它们像拼图一样组装起来,并看看每个旋钮该怎么调。
4.1 节点网络架构图(文字描述版)
由于无法使用图表,我用层级结构描述整个网络的数据流:
时间与空间输入层:
Time节点输出,经过两个不同的Multiply节点,生成SlowTime(系数~0.3)和FastTime(系数~0.7)。Position节点(Object Space)提供顶点原始坐标。
顶点位移生成层:
- 使用
SlowTime和Position的XZ分量,通过一系列Add、Multiply、Sine运算,生成一个低频、大振幅的波浪高度偏移量WaveOffsetA。 - 用同样的逻辑,但使用不同的方向和频率参数,生成另一个波浪高度偏移量
WaveOffsetB。 - 将
WaveOffsetA和WaveOffsetB用Add节点相加,得到总的顶点Y轴偏移。再与原始的X、Z通过Combine节点组合,输出到PBR Master的Vertex Position端口。
- 使用
法线细节生成层:
- 主波纹法线:
Texture 2D Asset(法线贴图) ->Sample Texture 2D。其UV输入来自:UV节点 ->Tiling And Offset(Tiling=0.05, Offset=(SlowTime*0.1, 0))->Normal Strength(Strength=0.8)。 - 细节涟漪法线:同一张或另一张
Texture 2D Asset->Sample Texture 2D。其UV输入来自:UV节点 ->Tiling And Offset(Tiling=0.15, Offset=(FastTime0.2, FastTime0.1))->Normal Strength(Strength=0.4)。 - 法线混合:
Normal Blend节点,将主波纹法线连入Base,细节涟漪法线连入Blend。输出为Final Normal。
- 主波纹法线:
颜色与光学计算层:
Fresnel Effect节点,输入Final Normal和默认View Direction,Power设为4。Color节点创建深蓝色(DeepColor,如#006994FF)和浅蓝绿色(ShallowColor,如#00FFE5FF)。Lerp节点,A连DeepColor,B连ShallowColor,T连Fresnel Effect输出。结果输出到PBR Master的Albedo。Final Normal输出到PBR Master的Normal。- 一个
Vector1节点(设为公开参数Smoothness,默认值0.9)输出到PBR Master的Smoothness。你也可以用Fresnel输出来进一步调制Smoothness,增加边缘反射。
4.2 关键参数调试心法
参数没有绝对的正确值,只有适合你场景的“感觉”。以下是调试顺序和逻辑:
先调“形”,再调“色”:首先关闭颜色和法线贴图,只保留顶点动画。调整波浪的
Speed(频率)和Amplitude(振幅),直到得到一个你觉得自然的基础起伏节奏。太大像海啸,太小像水塘。法线贴图强度:打开法线贴图,先将
Normal Strength都设为0,然后慢慢调高。主波纹的强度(如0.5-0.8)决定了波浪的“形体感”,细节涟漪的强度(如0.2-0.4)决定了表面的“粗糙度”。两者强度差不宜过小,否则层次不清。Tiling与Offset速度:
Tiling决定波纹的尺度。远景海面主波纹Tiling要小(如0.02-0.05),近景或特写可以大一些。Offset的速度要与顶点动画的Speed相匹配。通常,纹理滚动的视觉速度应略慢于或等于顶点起伏的速度,否则会产生“滑移”的失真感。菲涅尔与颜色:调整
Fresnel的Power,改变深水区与浅水区(亮部)的过渡范围。然后调整Lerp两端的颜色。DeepColor影响海水主体的颜色,ShallowColor影响边缘高光和水面反光的色调。晴天用明亮的蓝绿色,阴天用灰蓝色。光滑度与金属度:海水的
Metallic通常为0(非金属),Smoothness较高(0.85-0.95)。提高Smoothness能增强镜面反射高光的锐利度和环境反射的清晰度。你可以尝试将Fresnel输出连接到Smoothness,实现“边缘更光滑,中心更粗糙”的物理效果。
4.3 性能优化小贴士
- 贴图尺寸:用于海面的法线贴图,512x512分辨率通常足够,甚至可以尝试256x256。因为
Tiling通常很小,贴图会被拉伸,高分辨率收益不大。 - 节点简化:复杂的数学运算(如多个
Sine、Fraction)会带来性能成本。在满足效果的前提下,尽量简化顶点位移部分的计算。有时用一张高度图(Height Map)驱动顶点位移,性能可能更好。 - LOD支持:为海面材质配置多级LOD(Level of Detail)。在远处,可以使用更简单的Shader变体,例如减少一层法线混合,甚至关闭顶点动画,只用纹理动画。
5. 常见问题排查与效果进阶
即使按照步骤搭建,也可能会遇到一些奇怪的问题。这里记录了几个我踩过的坑和对应的解决方案。
5.1 水面闪烁或抖动(Z-fighting)
现象:海面与海底或其他物体交界处出现闪烁的像素。原因:海面网格与海底网格在深度(Z值)上过于接近,深度缓冲精度不足以区分谁在前谁在后。解决方案:
- 轻微抬高海面:在顶点着色器中,将海面整体向上(Y轴)偏移一个微小值(如0.001单位)。可以在顶点位移计算完成后,在最终的Y值上加一个很小的常数。
- 调整渲染队列:确保海面材质的渲染队列(Render Queue)在透明物体之前,但在不透明物体之后。通常设置为
Geometry+1(即2501)。 - 使用深度偏移:在ShaderGraph的
PBR Master节点或材质的Inspector面板中,调整Depth Offset参数。这是一个非常有效的解决方案。
5.2 波浪边缘出现锯齿(Aliasing)
现象:波浪的轮廓,特别是由顶点动画形成的尖锐波峰,在移动时出现闪烁的锯齿。原因:顶点位移幅度过大,导致三角形在屏幕空间的变化速率超过采样频率。解决方案:
- 开启抗锯齿:在项目质量设置或摄像机中确保MSAA或后处理抗锯齿(如SMAA、FXAA)是开启的。
- 柔化波浪:降低顶点动画的振幅,或者让波浪形状更平滑(例如使用
Sine函数而不是更尖锐的函数)。 - 增加曲面细分:如果使用支持曲面细分(Tessellation)的渲染管线(如HDRP),可以通过增加网格细分程度来提供更多的顶点细节,从而平滑波浪曲线。但这会显著增加性能开销。
5.3 法线贴图接缝明显
现象:流动的法线贴图在UV边界(通常是0和1的位置)出现明显的、不连续的接缝。原因:法线贴图没有设置为Wrap模式,或者Tiling不是整数导致边缘颜色对不上。解决方案:
- 检查导入设置:在Unity Project面板中选中法线贴图,在Inspector中确保
Wrap Mode设置为Repeat(重复),而不是Clamp(钳制)。 - 使用无缝贴图:确保你使用的法线贴图本身是“无缝”的,即它的左右边缘、上下边缘可以完美拼接。很多在线工具可以制作无缝纹理。
- 节点技巧:在ShaderGraph中,可以在UV进入
Tiling And Offset之前,先经过一个Fraction节点,确保UV值始终在[0, 1)范围内,避免因浮点数精度问题导致的接缝。
5.4 效果进阶:增加泡沫与焦散
一个顶级的海面还需要泡沫和海底焦散(Caustics)效果。
- 泡沫:可以基于波浪的“陡度”生成。在Shader中,计算顶点位移后相邻顶点的斜率或高度差,当差值超过某个阈值时,使用一张泡沫贴图进行混合。简单实现可以采样一张泡沫噪声图,用波浪的Y方向导数(通过
DDX/DDY节点近似)来驱动其显示。 - 焦散:这是光线透过水面在海底形成的光影图案。实现方法是在海底或水下物体的材质上,投影一张动态的、扭曲的焦散贴图。贴图的UV偏移可以用与海面顶点动画相似的
Time和Sine函数驱动,但速度更慢,以模拟光线的缓慢移动。
掌握了这五个核心节点和背后的原理,你就从被参数支配的“调试者”,变成了创造视觉规律的“设计师”。下一次,当你想实现某种特定的水面效果时,不妨先停下来思考:我需要驱动哪些属性?用什么节点来生成这种变化?如何混合多层信息?这个过程本身,就是Shader艺术最迷人的地方。