水面与海洋渲染
全库目录221
本文目录18
水面渲染由三个相对独立的子问题组成:网格如何组织、波形如何生成、表面如何着色。三者可以自由搭配——同一套波形可以跑在不同的网格方案上,也可以配不同的着色模型。分开理解比整体记忆某个方案更有用。
水面网格
海面理论上无限大,而顶点预算有限。核心要求是近处密、远处疏,且随相机移动时密度分布保持稳定。
投影网格
把一个规则网格铺在屏幕空间,再投影到水平面上求交点。
它的特性由构造方式直接决定:顶点在屏幕上均匀分布,因此投影到世界空间后天然近密远疏,且顶点总数恒定不随视角变化——预算完全可控。
代价是顶点的世界位置每帧都在变。依赖固定世界采样点的逐顶点缓存需要重投影或重建,所有信息必须能够从世界位置实时求出。相机接近水平时,投影范围趋于无穷,需要额外的边界处理。
分层网格
以相机为中心铺设若干层同心网格,每向外一层网格间距翻倍。相机移动时整体跟随,并按网格间距对齐吸附。
网格吸附稳定的是采样点:连续跟随相机时,顶点在波形上的采样位置不断变化,容易产生采样闪动。波形相位始终用世界坐标求值,即使网格移动,波本身也应固定在世界中。按间距取整吸附可以进一步稳定离散采样。
层与层之间的接缝需要处理。相邻层顶点密度差一倍,边界上稀疏侧的顶点必须与密集侧对齐,否则出现裂缝。常见做法是在过渡带插入专门的接缝三角形,或让密集侧的边界顶点插值到稀疏侧的位置。
四叉树细分
按相机距离递归细分平面,与地形 LOD 的思路一致。灵活性最高,可以按海岸线、重要区域局部加密,代价是需要维护树结构与相邻节点的层级差约束。
硬件曲面细分
提交一张粗网格,由细分阶段按屏幕投影尺寸动态加密。实现简单,但相邻补丁的边细分因子必须一致,否则出现裂缝;且细分因子需用小数模式避免密度跳变。
这些方案可以组合:用分层网格确定粗略结构,再用细分阶段做最后一级加密。
波形生成
Gerstner 波
Gerstner 波在正弦波的基础上增加了水平位移,让波峰变尖、波谷变平,比纯正弦更接近真实水面:
是 XZ 平面上的单位传播方向, 是长度单位的振幅, 是波数, 是弧度每秒的角速度。 是初相位, 控制水平位移比例。 过大时顶点会互相穿越,水面自交出现翻折。
法线可以解析求导得到,不必依赖差分——这是 Gerstner 相对高度图方案的一个优势。
叠加数个波即可得到可用效果,成本低,适合移动端与中小水体。
顶点评估
输入是平面网格的顶点位置与一组波参数,输出是偏移后的位置与解析法线。下面计算一个波。多波合成时,位置从原始平面位置开始,每个波只累加位移;两条导数分别从 (1,0,0)、(0,0,1) 开始,每个波只累加相对于这些单位基底的导数增量。若直接相加下方完整导数,会把单位项重复加入,法线随波数错误变平:
// 每个波携带:方向 dir(单位向量)、波数 k、振幅 A、角速度 w、陡峭度 Q
float3 pos = basePos;
float phase = k * dot(dir, basePos.xz) - w * time;
float s = sin(phase), c = cos(phase);
pos.xz += dir * (Q * A * c); // 水平位移,Q 控制波峰尖锐度
pos.y += A * s; // 竖直位移
// 解析法线:对 Gerstner 位移求偏导,不必用差分
float wa = k * A;
float3 tangent = float3(1 - Q * wa * s * dir.x * dir.x,
wa * c * dir.x,
-Q * wa * s * dir.x * dir.y);
float3 binormal = float3(-Q * wa * s * dir.x * dir.y,
wa * c * dir.y,
1 - Q * wa * s * dir.y * dir.y);
float3 normalWS = normalize(cross(binormal, tangent));验证方法是把 调大:波峰应变尖、波谷变宽; 过大导致顶点互相穿越时,能直观看到水面翻折。法线部分可把 设为 0 验证,此时法线应全部竖直向上。
FFT 海面
FFT 海面把大量规则波叠加到一张周期高度场中。输入是边长 米、每边 个采样点的水平正方形;采样间距为 ,波数间距为 。整数索引 对应 ,空间采样点是 。增加 能容纳更长的波,固定 时也会降低可表示的最高频率;增加 才能在相同范围内保留更短的波。
波谱与随机种子
Phillips 谱提供各波数的相对强度。令 、单位风向为 、风速为 ,取 :
是独立标准正态数,初始化后固定; 抑制短于所需尺度的细波。 的种子直接置零,避免除零并固定平均水位。这里把 定义为离散种子的方差, 的单位和数值随这一约定标定;若输入是连续波谱密度,还须乘波数单元面积再转换为离散方差。改变网格尺寸后直接沿用未经标定的连续谱数值,会改变总波高。
风向垂直的波在该谱下权重为零。平方点积同时允许顺风和逆风分量,它本身没有把能量全部限制到顺风半平面。每帧重新抽随机数会让波形跳动;稳定动画来自固定种子的相位演化。
时间演化与实数约束
深水重力波取 ,每帧构造:
负波数必须以取模索引读取,例如数组索引 的配对位置是 。两项共同保证 ,于是空间结果为实数。只旋转独立随机复数而不构造配对,会产生明显的虚部;直接丢弃虚部会掩盖输入不满足约束的错误。
上述空间和没有归一化因子。若库的二维 IFFT 自带 ,输出需乘 ;若使用已经按该归一化标定的频谱,则保持库的约定。频谱以数组零号存零频时,可直接使用标准 IFFT;以中心存零频时先做索引移位,避免空间结果出现交错符号。偶数网格的 Nyquist 行、列在离散负索引下映射到自身,奇数阶导数需要特殊处理;便于审查的实现将这些种子置零,使高度和导数场共享同一实数约束。
二维 IFFT 可以先变换每行,再变换每列。每条长度 的一维逆变换有 级蝶形:长度 的子问题把偶序列变换 和奇序列变换 合成 与 。每级输出到另一张缓冲后交换读写,行阶段完成后列阶段读取其结果;GPU 实现须保证级间写入可见。高度、位移与导数字段可以打包变换,但打包和解包仍要遵守各字段的共轭关系。
位移、导数与法线
高度的两个斜率在频域直接乘 、 再逆变换。没有水平位移时,法线为 。在接近采样极限的短波处,中心差分的频率响应是 ,它会低估真实的 ,因此谱导数和低分辨率高度差分并不等价。
尖峰效果使用 ,零频取零;逆变换得到 ,表面位置为 。每个 的空间偏导同样在频域乘对应 。两条切向量因此为:
法线取 。只用高度斜率求法线,会遗漏水平压缩后的表面朝向。水平雅可比为 ,接近零说明局部压缩,变为负值说明翻折。
取 米,只保留 两项且振幅各为0.5米,某帧四个采样高度是 米,谱导数是 。水平位移为 米;取 ,波峰处雅可比为 ,取 则为 ,表面翻折。这个例子同时检验归一化、导数符号和尖峰方向。
FFT 生成的是给定统计谱的周期表面。浅水色散可用 表示恒定水深 的传播;变化岸线、船体尾流和破碎飞沫需要额外动力学。贴图周期性则由有限波数网格直接决定。
平铺重复
无论是少量 Gerstner 波还是 FFT 生成的贴图,远景俯视时都会暴露明显的重复图案。人眼对规则重复极其敏感,这是水面观感的主要破绽之一。
几种应对,通常叠加使用:
多尺度叠加。用两到三套不同周期、不同旋转角度的波形叠加。不同尺度与角度能让重复特征更难辨认。只有周期能写成同一基础单位的整数倍、方向也兼容时,才能按最小公倍数讨论精确重复周期;旋转后的二维图案通常不能用这条一维结论概括。
波数与方向的选取。Gerstner 方案中,避免各波的频率成简单整数比、方向过于集中。方向分布在一个主风向附近的扇形内比完全随机更真实,也更不容易形成规则干涉图案。
低频扰动。用一张大尺度的低频噪声调制波的振幅或 UV,打破规律性。成本低,效果明显。
距离衰减。远处本就看不清细节,随距离降低高频波的振幅,同时提高法线贴图的 Mip级别。这既减轻了重复感,也缓解了远处法线的高频闪烁。
最后一点值得单独强调:远处水面的法线闪烁是普遍问题。大量亚像素级的高频法线在时域上剧烈跳变,即使有时域抗锯齿也难以稳定。让远处的法线收敛到平均朝向(接近水平)是必要的处理。
表面着色
反射与折射
水面的反射率随视角变化剧烈:俯视时主要看到水下,掠射时接近镜面。这由 Fresnel 项描述,是水面观感最重要的单一因素。
环境贴图查询成本较低,但内容更新受捕获策略限制。平面反射增加一个镜像视图,成本取决于分辨率、剔除和所启用的通道;屏幕空间反射复用已有画面,却缺少屏幕外和遮挡后的内容。三者没有固定的成本排序。实践中常以屏幕空间反射为主,失败处回退到环境贴图。
折射通常用扰动后的 UV 采样当前场景颜色实现。两个必须处理的细节:采样位置要按水下深度与折射强度偏移,且必须防止采样到水面之前的物体——否则岸边会出现水下颜色渗到岸上的错误。做法是比较采样点的深度,若该处物体在水面之上则退回未扰动的 UV。
水体颜色与透光
水色由选择性吸收、体散射和入射环境共同决定。清水在可见光范围常对长波红光吸收较强,传播越远,底色的光谱变化越明显;悬浮物与藻类还会改变吸收和散射,水体颜色不能仅由深度决定。
简化模型是按水面到水底的距离做指数衰减,对 RGB 使用不同的衰减系数:
是光在水中的路径长度;屏幕实现常用同一线性观察空间的水面与水底深度差近似,但斜视和折射时它不等于真实路径, 是三个通道各自的衰减系数。这比直接用透明度混合更符合观察——浅滩自然透出底色,较深时收敛到本模型的等效散射颜色。这里的Cwater表示均匀源项下的沿途散射近似;只有吸收、没有入射散射源时应取零,吸收本身不会生成非零的水色。
泡沫
泡沫出现在两类位置,判据不同。
波峰泡沫。判据不是高度,而是表面的形变程度。Gerstner 波中可以用雅可比行列式判断水平压缩程度,压缩剧烈处即波峰破碎位置;更简化的做法是用法线的偏导数或曲率,变化剧烈处生成泡沫。
用高度做判据是常见的错误,它会让所有波峰不分强弱地一律泛白,看起来像一层白纱而非破碎的浪花。
岸边泡沫。由水面到岸边地形的距离决定,通常从深度缓冲比较得到。距离越近泡沫越强,并叠加一张随时间流动的噪声表现拍岸的动态。
两类泡沫可以共用一张泡沫纹理,但强度遮罩的来源完全不同。
对于单个波,水平映射的雅可比行列式为 ; 能保证这份单波水平映射不翻折。多波叠加时要对合成导数求行列式,不能逐波通过就宣称整体安全。泡沫可在较小的 J 区域增加,但这只是破碎倾向的艺术判据,不是空气卷入量的物理解。
岸边过渡
浅水区需要同时处理几件事:水体颜色变淡透出底色、泡沫增强、以及波高衰减——浅水波会发生折射、浅化和破碎,不能把深水波直接延伸到岸上。
波高衰减容易被忽略,缺失时会看到大浪直接穿过沙滩的穿帮。一种便于美术控制的近似是按水深衰减振幅,使岸线处回到静止水面。它用于避免穿岸,完整浅水动力学还要描述浅化和破碎。
验证方法
- 相机贴近水面平移,检查波形是否钉在世界空间中,出现整体滑动时先检查波形是否误用相机局部坐标,再检查采样吸附。
- 拉到高空俯视,观察是否出现规则重复图案。
- 沿网格分层边界检查是否有裂缝或密度突变。
- 在浅滩处检查水体颜色是否平滑过渡、波高是否随水深衰减。
- 关闭折射扰动对比,检查岸边是否有水下颜色渗到岸上。
- 远景静止观察法线闪烁,确认距离衰减生效。
- 单独输出泡沫遮罩,确认波峰泡沫只出现在形变剧烈处而非所有波峰。
本机机制检查:30 组固定种子的 CPU 输入中,解析切向量与中心差分吻合;这只验证单波公式,没有执行水面着色器。检查代码见 reference.py,运行 python 知识库/examples/mechanisms/reference.py。引擎画面、GPU捕获与性能另外验证。FFT 的共轭演化、四点空间和与谱导数另由 独立参考模型 检查,运行 python 知识库/examples/mechanism_depth/water_atmosphere/test_reference.py;该模型直接求有限傅里叶和,尚未执行 GPU 蝶形变换。
相关主题
参考资料
-
GPU Gems 第1章:Effective Water Simulation from Physical Models:1.2 的几何波与纹理波、偏导及 Gerstner 波;正文相位与采样网格分别定义。
-
Tessendorf:Simulating Ocean Water:§4.3—4.4 的波谱与相位演化,§4.6 的水平位移;正文另行明确数组、归一化与教学算例约定。