GPU Gems 1 · Chapter 1. Effective Water Simulation from Physical Models

以物理有意义的波参数,在 GPU 上叠加几何波面与动态法线贴图,构成可控的实时水面系统。

学习目标

  • 能用波长、振幅、速度、方向和相位写出可采样的水面高度函数,并解释每个参数的视觉意义
  • 能从高度场的两个偏导数构造切线、双切线和法线,说明为什么几何波与法线贴图要分层
  • 能区分正弦波、Gerstner 波、方向波与圆形波的适用场景,并用水深和网格边长过滤参数
  • 能把模型落到 GPU 流水线:顶点阶段更新几何、像素阶段生成动态法线并完成反射合成

为什么水面需要两层模型

如果把所有细节都交给顶点几何,网格必须密到能够表达最短波,远处会浪费大量顶点;如果只保留法线贴图,水面轮廓和遮挡永远是平的。两层模型把低频形状交给几何,把高频方向交给纹理,正好对应 GPU 在顶点和像素阶段的不同采样能力。

目标:用少量参数让水面持续有生命

GPU Gems 第1章研究的是大到海湾、小到池塘的实时水面。系统把水面拆成两类互补信号:顶点阶段更新低频的几何起伏,像素阶段在 render target 中生成高频的动态法线贴图。两者都来自同一族周期函数,但服务于不同的采样尺度。

这不是严格求解 Navier–Stokes 方程的流体模拟,而是一个有物理依据、能在 GPU 上稳定运行的近似。正因为参数对应波长、振幅、速度和风向,艺术家可以脚本化地改变天气;正因为所有波都能独立推进,水面不会像平铺的一张静态贴图。

1. 用正弦波表示高度

先把水平面上的位置记为 X=(x,y),时间记为 t。一条方向波可以写成:

hi(X,t)=Aisin(ωi(DiXSit)+ϕi).h_i(X,t)=A_i\sin\left(\omega_i(D_i\cdot X-S_i t)+\phi_i\right).

其中 A_i 是振幅,D_i 是水平传播方向,S_i 是速度,φ_i 是初相位,ω_i 控制空间频率。用波长 L_i 时,常见的归一化约定会令空间频率与 1/L_i 成正比;实现中只要前后一致,关键是波峰间距和相位推进保持可控。

总高度是多条波的叠加:

H(X,t)=i=1nhi(X,t).H(X,t)=\sum_{i=1}^{n}h_i(X,t).

这条函数同时给出每个水平位置的高度和表面朝向。几何网格只在顶点位置采样 H;比网格更细的变化则在纹理空间重新采样同类函数,生成动态法线。

H(X,t):从多条波到一张水面h₁:长波h₂:中波h₃:短波H = h₁ + h₂ + h₃ + …
一组物理有意义的周期波叠加,比一张静态噪声贴图更容易控制和扩展。

参数不是装饰:它们决定观感

决定尺度:长波负责海面的缓慢起伏,短波负责细碎波纹。 决定高度范围;振幅与波长的比例过大,波峰会显得不可信。 决定相位随时间如何移动; 把波与风向或水流联系起来。

为了让系统持续变化,可以让一条波逐渐淡出,再用新的一组参数淡入。每次重新生成都要同时约束波长、振幅和速度,否则独立随机会产生相互矛盾的视觉节奏。

2. 从高度场得到表面方向

令三维表面为:

P(x,y,t)=(x,y,H(x,y,t)).P(x,y,t)=(x,y,H(x,y,t)).

xy 求偏导,得到两个切向基向量:

B=Px=(1,0,Hx),T=Py=(0,1,Hy).B=\frac{\partial P}{\partial x}=(1,0,H_x),\qquad T=\frac{\partial P}{\partial y}=(0,1,H_y).

两者的叉积给出未归一化的表面朝向:

N=normalize(B×T)=normalize((Hx,Hy,1)).N=\operatorname{normalize}(B\times T)=\operatorname{normalize}((-H_x,-H_y,1)).

这里的不是额外的近似采样,而是由显式高度函数直接计算。因为正弦函数的导数仍是正弦/余弦,H_xH_y 仍然可以按波逐条累加。

从 H(x,y,t) 直接构造法线B = ∂P/∂xT = ∂P/∂yN = normalize(B×T)P(x,y)=(x,y,H)Hx、Hy → 切线 → 法线继续送入反射与光照
高度场的两个偏导数直接提供切线空间基,避免为每个顶点用邻居做有限差分。

从法线还能构造切线空间的基。对小波纹来说,逐像素更新法线比继续细分几何更划算;这就是为什么同一个高度模型会同时出现在顶点 shader 和像素 shader 中。

3. 几何波与纹理波分工

几何层只保留少量低频波:它改变轮廓、遮挡和水面与岸边的关系。纹理层叠加更多频率和方向的波,在 256×256 左右的 render target 中生成。水面看起来丰富,主要依赖这层高频细节,而不是把主网格无限细分。

一套模型,两个采样尺度几何层少量低频波改变轮廓与遮挡法线层多频率纹理波改变局部光照合成层环境反射 + 水色得到动态水面顶点 shader:位置 / 基向量 像素 shader:法线 / 反射细节不必全部付出几何顶点的代价
几何负责看得见的轮廓,法线贴图负责网格以下的细节;分层让 GPU 预算可控。

正弦波与 Gerstner 波

直接叠加正弦波的波峰比较圆,适合平静池塘。需要更尖的波峰和更宽的波谷时,可以使用。它不只改变高度,还把顶点向波峰方向移动,因此能把采样点集中到高频特征附近。

Gerstner 波的陡峭度参数通常记为 QQ 太大时,波峰可能形成回环;生产工具应把它限制在安全范围,并让 Q 与波数、振幅和波的数量共同约束,而不是把它当作无上限的“尖锐度滑杆”。

方向波适合风驱动的大水面,方向向量在波的生命周期中保持稳定;圆形波适合瀑布落点或局部扰动,方向由波心指向顶点的向量实时归一化得到。两者共享同一套采样流程,差别在于方向来自常量还是来自空间位置。

4. 水深与网格边长进入 authoring

水面网格的顶点除了水平位置,还可以携带水底高度,从水面高度减去水底高度得到。水深可以同时控制透明度、反射强度和几何波幅:浅水区域更透明,靠岸处波幅逐渐衰减,顶点不会穿过岸线。

Authoring:让内容数据参与物理近似顶点输入水底高度局部边长颜色覆盖纹理坐标 / 水面高度过滤与衰减水深 → 靠岸波幅边长 → 短波响应颜色 → 透明/反射GPU顶点像素合成
水深和网格边长不是隐藏的魔法数字,而是可解释、可调试的内容输入。

边长滤波与纹理坐标

局部网格边长决定几何层能表达的最短波长。若波长约为边长的四倍,通常可以保留完整响应;当波长接近边长的两倍时,应把响应衰减到零。这种可以避免短波混叠,同时允许重要区域使用更密的三角形、远处区域使用更粗的网格。

普通池塘可以从顶点位置推导纹理坐标;蜿蜒河流则需要显式纹理坐标和切线空间变换,把纹理中的法线旋转到弯曲河道的世界空间。对每个顶点统一采用同一处理流程,差异只来自 authoring 数据和过滤参数。

5. GPU 运行时流水线

顶点阶段先计算几何波,按水深和边长对波幅衰减,再输出表面位置、切线空间基和与环境贴图相关的向量。像素阶段从动态法线贴图取样,把纹理空间法线变换到水面空间,用它查询环境反射,再和水本身的颜色、透明度合成。

一个高层伪代码如下:

vertex:
  depth = waterHeight - bottomHeight
  H, dHdx, dHdy = sumGeometricWaves(position, time, filteredParameters)
  normalBasis = basisFromPartials(dHdx, dHdy)
  emit displacedPosition, normalBasis, depth
 
pixel:
  detailNormal = sampleDynamicNormalMap(uv, time)
  worldNormal = transform(detailNormal, normalBasis)
  reflection = sampleEnvironment(worldNormal, correctedEyeVector)
  output = blend(waterColor(depth, viewAngle), reflection)

其中 correctedEyeVector 解决小水池环境贴图中心与观察点不一致的问题;对无限远天空,简单环境向量已经足够,对能看到对岸物体的池塘,则需要按水体范围修正反射方向。

Water Wave Lab

先调一条波,再理解参数耦合

可交互
H(x,t) = A sin(ωx − St)振幅/波长 = 0.050:越大,波峰越陡

6. 一次可验证的调参顺序

  1. 先预测:只改变波长、振幅和速度时,你预期波峰间距、垂直高度和移动节奏分别如何变化;再固定四条几何波的波长和风向,确认轮廓移动稳定。
  2. 再给每条波分配振幅和速度,检查振幅/波长比例与波峰形状。
  3. 添加纹理波,先覆盖不同频率,再调整方向以避免重复图案。
  4. 用水深衰减靠岸波幅,用边长滤波关闭网格无法表达的短波。
  5. 最后调透明度、反射和 Fresnel 近似,避免把几何问题误判为材质问题。

本章回顾:一套有物理依据的近似

  1. 水面可以用多条周期波的高度和来近似;参数对应波长、振幅、速度、方向和相位。
  2. 高度场的两个偏导数直接给出切线空间基,从而得到法线和动态光照所需的方向。
  3. 少量几何波负责轮廓,更多纹理波负责法线细节;两层不应互相替代。
  4. Gerstner 波通过顶点水平位移提高波峰陡峭度,但陡峭度必须避免形成回环。
  5. 水深控制靠岸衰减,边长滤波控制几何采样能力,二者都是 authoring 数据的一部分。
  6. 顶点 shader 更新几何,像素 shader 生成法线并合成反射;GPU 承担计算,CPU 保留给游戏逻辑和 AI。

练习:从公式走到 shader

练习

问题 1 单条波取 A=0.2、波长 L=4、速度 S=1,说明增大 ALS 各自会怎样改变水面观感,并指出不能只独立随机它们的原因。

问题 2 已知高度场 H(x,y)=0.2sin(x)+0.1sin(2y),写出 H_xH_y,再写出未归一化法线的方向。

问题 3 为什么几何波只保留少量,而纹理波可以使用更多频率?请把“轮廓”和“局部光照”分别对应到两层。

问题 4 一个靠岸顶点的水深接近零,且局部网格边长很大。应如何处理波幅?这同时解决了什么视觉问题?

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

波长

相邻波峰之间的世界空间距离,决定波的尺度。

振幅

从静水面到波峰的高度,决定波的垂直起伏范围。

偏导数

高度场沿水平坐标某个方向的变化率,用于构造表面方向。

法线贴图

记录局部表面朝向的纹理,用于在不增加几何的情况下丰富光照细节。

水深

水面高度与水底高度之间的局部距离,可控制透明度、反射和波幅。

边长滤波

根据网格局部边长衰减无法被几何采样的短波,避免混叠。

速度

波峰沿传播方向每秒前进的距离,决定相位随时间推进的节奏。

方向

波峰在水平面中前进的单位向量,通常与风向或水流有关。

Gerstner 波

通过水平顶点位移控制波峰陡峭度的物理启发波函数。

讨论

评论区加载中…