这个项目是对真实感水体模拟与渲染的系统性研究,是我与 Moussab Ibrahim、Weier Flora Xiao 合作为 CMU《视觉计算系统》课程撰写的论文。我们实现并对比了实时水体技术的几大主要流派——从程序化波形叠加到 FFT 海面频谱,从粒子流体到欧拉网格求解器——全部在 Unity 中用 compute shader 做了 GPU 加速。
完整论文内嵌在页面底部,下面是它所覆盖内容的导览。
表面波浪:从振荡器到 FFT 海面
正弦叠加与 Gerstner 波
最简单的水面是由正弦振荡器驱动的高度场。单个正弦波看起来明显是假的,但叠加不同频率、振幅和方向的多个波形,就已经能呈现出平静水面的样子:
然而真实的波浪有尖锐的波峰和宽阔的波谷,纯正弦叠加做不到这一点。Gerstner 波通过让顶点同时做水平位移来解决——顶点在波峰处聚拢,形成经典的摆线轮廓,至今仍是海面着色的标准手法。
基于 Phillips 谱的统计海洋建模
任何有限的周期波叠加,从高处俯瞰终究会暴露出重复的平铺感。海洋学研究则把波高视为具有统计规律的随机变量。沿用 Tessendorf 的方法,我们在傅里叶空间中合成波高场:每个波矢 对应一个由高斯噪声采样的复振幅,其分布由 Phillips 谱塑形:
其中编码了风速 和风向 ——垂直于风向的波会被抑制,短波长尾部收敛性差的问题则用高斯因子 压制。
时间演化来自色散关系 :深水中不同波长的波以不同速度传播,这正是海面看起来"活"的原因:
让它实时化的 FFT
每帧都要把频谱变换回空间高度场,意味着在 512×512 网格上做逆 DFT—— 完全不可行, 则绰绰有余。论文从第一性原理推导了 Cooley-Tukey FFT:复数作为旋转、欧拉公式、四点蝶形运算、旋转因子的对称性,以及分治的各个阶段如何组装成"先水平后垂直"的一维 FFT Pass,最终在 GPU 上生成高度图。
体积流体:粒子与网格
高度场无法飞溅。为了得到真正三维的流体行为,我们实现并对比了三种模拟范式,全部用 Unity compute shader 并行化。
位置基流体(PBF)
PBF 直接在位置上工作,绕开了基于力的 SPH 的刚性问题:先用速度和重力预测粒子位置,再迭代地把它们投影到不可压缩约束上:
其中密度 用 poly6 平滑核在邻居上估计,位置修正 通过逐粒子的拉格朗日乘子分配。均匀网格邻居搜索(格子边长 = 交互半径,27 格查询)把邻居查询从 降到近似 ——这是数万粒子能保持交互帧率的关键。
光滑粒子流体动力学(SPH)
Navier-Stokes 动量方程的经典力学形式:压力、粘性力和密度全部通过平滑核求值(密度用 poly6、压力用 spiky 梯度、粘性用拉普拉斯核)。借助 GPU indirect instancing,我们实时跑到了约一万个粒子。SPH 能漂亮地捕捉水滴和飞溅,但对时间步长和刚度参数极其敏感——这些"有教育意义的痛苦"正是我们转向 PBF 的动机。
欧拉网格求解器
相反的世界观:固定空间而不是粒子。速度和密度存在交错网格上;每步先做半拉格朗日平流,再用 Gauss-Seidel 压力投影强制 。在 GPU 上并行化 Gauss-Seidel 并不平凡,因为相邻格子共享边——我们采用棋盘分区把格子分成两个互不相交的组,让每半张网格并发更新而不产生竞态。网格方法明显比 SPH 稳定,规模也超过了一万格子。
屏幕空间流体渲染
粒子不是表面。为了把 PBF 模拟渲染成连续的水体,我们实现了屏幕空间流体渲染管线:
- 粒子球体——几何着色器把每个粒子展开成朝向相机的四边形,片段着色器丢弃球外像素。
- 深度 Pass——渲染粒子深度并编码供后续使用。
- 双边滤波模糊——在保留轮廓的同时平滑深度缓冲,把一颗颗粒子"熔"成连贯表面。
- 法线重建——从深度梯度经叉积重建视空间法线。
- 厚度 Pass——关闭深度测试的加法混合,度量每条视线穿过多少流体。
- 着色——漫反射 + 环境光 + 高光,用 Beer-Lambert 衰减 做体积吸收,用 Schlick 近似计算 Fresnel 反射。
最终效果是一个透明、有折射感的液体体积——完全从粒子数据计算而来,没有任何网格化步骤。
这个项目展示了什么
- 覆盖水体技术栈的广度——程序化波形、频谱海面合成、三种流体模拟范式、完整的屏幕空间渲染管线,全部亲手实现而非纸上综述。
- 效果背后的数学——从零推导复分析与 FFT;理解每种频谱、色散关系和平滑核的适用边界。
- GPU 工程判断力——compute shader 并行化、无竞态的 Gauss-Seidel、网格邻居加速,以及对 SPH、PBF 和欧拉方法在稳定性、细节与可扩展性之间的坦诚权衡分析。
完整的 13 页论文:
Realistic Water Simulation — 完整论文Open ↗
