站内搜索

输入关键词开始搜索

Three.js FFT 真实海面:WebGPU、WebGL 与多级联波谱

文章目录

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

Three.js FFT 真实海面、中文调试面板和运行状态

实现思路导图

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 猜原因。

一帧实际发生什么

  1. 时钟推进,更新频域相位。
  2. 各级联完成二维逆 FFT,生成位移、导数和泡沫输入。
  3. 海面网格采样各级联,完成顶点位移与水体光照。
  4. 采样器更新浮船和喷溅等附属对象。
  5. 后处理输出最终画面,并汇总后端、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 轴和复数相位符号。
  • 泡沫一直累积:为历史泡沫加入时间衰减和阈值。
  • 船体高频抖动:减少短波采样权重并使用与帧率无关的阻尼。

相关文章

Three Tile FFT 海面:地图瓦片与频谱海浪的组合渲染

在 three-tile 地图场景中叠加 WebGL FFT 海面,并处理地图与海面视角、MRT、质量预设和运行统计