文章目录
Poseidon 海面案例使用频谱方法生成大范围动态海浪,并提供 WebGPU 与 WebGL 后端。画面、调试面板和运行统计来自同一套海洋运行时。

实现思路导图
flowchart TD
ROOT[Poseidon FFT 海面]
ROOT --> CASCADE[多级联频谱]
CASCADE --> H0[高斯随机 h0]
CASCADE --> EVOLVE[时变频谱]
ROOT --> BACKEND[GPU 后端]
BACKEND --> WEBGPU[WebGPU Compute]
BACKEND --> WEBGL[WebGL RenderTarget]
ROOT --> OUTPUT[位移导数与泡沫]
ROOT --> IMAGE[海面材质与天空]
ROOT --> BOAT[浮船同源采样]
ROOT --> QUALITY[质量统计与后端切换]
截图展示的是已运行的三层级联波场,状态面板给出了实际后端、256 FFT、计算 pass、网格和三角面数量。海面上的大尺度起伏与近景细波来自不同级联,不是几张滚动法线贴图叠加出来的动画。
页面只选择后端和质量
页面把渲染器模式、质量和 MRT 开关交给场景组件:
<PoseidonOceanScene
:renderer-mode="rendererMode"
:quality="quality"
:use-m-r-t="useMRT"
@ready="handleReady"
@stats="handleStats"
/>
ready 返回场景 API,控制面板通过 API 更新海面参数或添加浮船,不直接持有 Three.js 内部对象。
运行时把页面状态和 GPU 资源隔开。页面可以修改 waveHeight、风向或材质颜色;运行时决定这是一次 uniform 更新、初始频谱重算,还是必须重建渲染器。组件卸载只调用统一的 dispose,避免 Vue 层逐个猜测纹理和计算节点的所有权。
海浪从频域开始
初始频谱根据风向、风速和波数生成复数振幅。每帧只更新相位,然后执行二维逆 FFT,输出位移、斜率和泡沫需要的导数信息。
多个级联使用不同覆盖范围:长波决定大形态,短波补近景细节。截图中的运行状态显示三个级联与 256 FFT 尺寸。
每个级联先为离散波数 k 生成高斯随机复数,再乘频谱能量得到 h₀(k)。时间相关频谱为:
h(k,t) = h₀(k)e^(iωt) + h₀*(-k)e^(-iωt)
ω² = g|k|tanh(|k|d)
逆 FFT 把它变回空间域高度。水平位移来自频谱沿 k 方向的分量;斜率和 Jacobian 也在频域乘导数因子后逆变换。这样一次频谱能同时产出位置、法线和浪尖压缩信息。
级联不是简单缩放同一张纹理。每级有独立世界长度与波数带,短级联过滤掉它表达不了的长波,长级联也不承担超过采样分辨率的细波。材质按世界坐标采样并合成,避免相机移动时出现纹理跟随相机滑动。
this.renderer.compute(this.timeDepGroup);
for (const group of this.stepGroups) {
this.renderer.compute(group);
}
this.renderer.compute(this.assembleGroup);
WebGPU 的执行顺序是:初始化频谱 compute → 每帧时变频谱 compute → 横向蝶形阶段 → 纵向蝶形阶段 → 位移/导数组装。各阶段使用 storage texture 或 buffer 在 GPU 内传递,CPU 只更新时间和参数,不读取整张波场。
WebGPU 与 WebGL 共用参数语义
WebGPU 后端使用计算管线,WebGL 后端通过多次全屏 pass 和纹理 ping-pong 完成同类计算。两边必须保持纹理通道、归一化和坐标方向一致,否则切换后端时波高、法线或泡沫会改变。
MRT 能在一次绘制中输出多张结果纹理,减少 pass 数,但需要设备和后端支持。自动模式先检查能力,再选择可运行路径。
WebGL 没有通用 compute shader,所以用正交相机绘制全屏三角形,把计算结果写到浮点 RenderTarget。每个 FFT 阶段在两张纹理间 ping-pong。MRT 可让位移和导数等多个目标共享一次几何提交,但并不会减少纹理像素计算量;设备不支持浮点多附件时必须回退到多 pass。
两个后端共用同一参数模型,但不能共享 GPU 对象。切换后端意味着销毁旧 renderer、RenderTarget/StorageTexture、材质与管线缓存,再从相同参数重建;只替换一个布尔值无法让现有资源跨 API 继续使用。
水面材质
顶点阶段读取水平与垂直位移。片元阶段组合动态法线、Fresnel、天空反射、深水颜色和泡沫。泡沫通常根据位移 Jacobian 或浪尖压缩程度生成,而不是一张固定滚动贴图。
材质顺序也有因果关系:先位移顶点,再用导数恢复法线;用视线与法线算 Fresnel;用太阳方向求高光;最后把深水散射、天空反射、次表面透光和泡沫混合。若先用静态几何法线计算光照,波峰位置变了而高光不变,画面会像透明塑料布。
泡沫保留上一帧历史并逐步衰减。新泡沫由 Jacobian 低于阈值的压缩区域注入,因此总出现在折叠浪尖附近;衰减过慢会铺满海面,过快则只剩闪烁白点。
浮船
船体采样海面高度和法线,得到位置与姿态。采样器必须读取当前级联结果,并与渲染帧时间同步。场景 API 统一管理模型加入和清理,页面切换时不会留下旧船体。
船体不能只采一个中心高度。运行时取船首、船尾、左舷、右舷和中心五点,利用前后差计算俯仰、左右差计算横摇,再对位置和四元数做阻尼插值。船体只响应低中频级联可以减少短波导致的高频抖动。
质量档位
质量同时影响 FFT 尺寸、级联、网格和采样数量。运行面板展示实际后端、帧率、pass 数与三角面数,便于判断瓶颈来自计算还是几何。
判断瓶颈时要分别看两条链:FFT 分辨率与级联数决定计算纹理成本,海面网格决定顶点和光栅化成本。降低网格无法减少 FFT pass,降低 FFT 也不会减少三角面。截图中的统计把两类数字同时列出,就是为了避免只看 FPS 猜原因。
一帧实际发生什么
- 时钟推进,更新频域相位。
- 各级联完成二维逆 FFT,生成位移、导数和泡沫输入。
- 海面网格采样各级联,完成顶点位移与水体光照。
- 采样器更新浮船和喷溅等附属对象。
- 后处理输出最终画面,并汇总后端、pass 和三角面统计。
主线是:频谱生成 → GPU 逆 FFT → 多级联位移与导数 → 海面光照材质 → 同源采样驱动浮船。
从零复现:Poseidon 海面教程
案例源码位于 src/demos/threedemos/poseidon-ocean/。推荐先完成 WebGL 单级联,再增加多级联,最后实现 WebGPU 后端;否则很难判断问题来自频谱、FFT 还是后端差异。
第 1 步:建立静态海面与天空
创建规则 PlaneGeometry、透视相机、方向光和天空。先用静态法线验证颜色空间、曝光和相机近远面。
海面几何使用世界米制尺寸,Shader UV 由世界位置和级联长度计算。不要依赖模型自身 0–1 UV,否则相机移动或海面扩展时波形尺度会改变。
第 2 步:实现高斯随机初始频谱
使用 Box-Muller 等方法生成稳定高斯噪声,为每个 k 计算目标频谱能量和复数 h₀(k)。保存随机种子、风向、风速、水深和阻尼。
const amplitude = Math.sqrt(spectrum(k) * 0.5);
h0[index] = complex(gaussianA * amplitude, gaussianB * amplitude);
先画频谱调试图。若 k=0 没有置零,会出现整片海面上下漂移;若正负波数不满足共轭关系,逆变换会产生不可解释的虚部误差。
第 3 步:完成单级联二维 FFT
实现时变频谱 pass、横向蝶形阶段、纵向蝶形阶段和最终排列修正。WebGL 用浮点 RenderTarget ping-pong;WebGPU 用 storage texture 与 compute group。
测试不要直接看海面。先把逆 FFT 高度输出为灰度图,用 CPU 小尺寸 DFT 或已知单频输入比对符号、索引和归一化。
第 4 步:输出位移、导数和 Jacobian
频域中分别构造垂直位移、水平位移、斜率和水平位移导数,逆 FFT 后组装到纹理通道。Jacobian 描述水平映射的压缩,低值区域注入泡沫。
完成标志:增加 choppiness 后浪尖向前倾斜;法线高光紧跟浪峰;泡沫只出现在压缩浪尖而不是随机铺满水面。
第 5 步:扩展为多级联
为长、中、短三个尺度分别设置世界长度和有效波数带。各级联独立计算,再在顶点和片元阶段按世界位置合成。
相邻级联需要频带过渡,避免同一波段重复计入导致能量过强。调试时一次只开一个级联并显示其位移范围。
第 6 步:实现水面材质
顶点阶段叠加各级联位移。片元阶段按顺序计算动态法线、Fresnel、天空反射、太阳高光、深水散射、次表面透光和泡沫。
确保全项目只有一次色调映射。海面材质、后处理和 renderer 若重复处理颜色,会导致高光过曝或海水发灰。
第 7 步:提供 WebGPU 与 WebGL 后端
两边共用参数定义、频谱公式和纹理通道语义。运行时先检测 WebGPU 和浮点纹理/MRT 能力,再选择后端。切换时销毁旧 renderer 与全部 GPU 资源后重建。
同一随机种子和时间下截取两个后端的高度纹理,比较统计范围与主波方向。允许浮点细节差异,不允许浪高倍率或坐标方向不同。
第 8 步:加入浮船与质量统计
用中心、首尾和左右五点采样低中频波场,计算升沉、俯仰和横摇,再做指数阻尼。统计面板分别显示 FFT 尺寸、级联、pass、网格三角形和实际后端。
验收与排错
- 海面周期性重复明显:增加级联尺度差异与细节法线,不能只放大单张波纹。
- WebGPU 与 WebGL 浪向相反:检查纹理 Y 轴和复数相位符号。
- 泡沫一直累积:为历史泡沫加入时间衰减和阈值。
- 船体高频抖动:减少短波采样权重并使用与帧率无关的阻尼。