海洋模拟项目源码解析

静态效果

项目文件组成结构如下:

复制代码
├── LICENSE.md

├── makePG.py

├── OceanDemo-main.zip

├── package.json

├── README.md

├── src

│ ├── assets

│ │ ├── environment

│ │ │ └── qwantani_1k.hdr

│ │ └── ocean

│ │ ├── 00_noise0.exr

│ │ ├── babylonBuoy.glb

│ │ ├── buoy.glb

│ │ ├── fftInverseFFT.wgsl

│ │ ├── fftInverseFFT2.wgsl

│ │ ├── fftInverseFFT3.wgsl

│ │ ├── fftPrecompute.wgsl

│ │ ├── fisher_boat.glb

│ │ ├── foam1.jpg

│ │ ├── initialSpectrum.wgsl

│ │ ├── initialSpectrum2.wgsl

│ │ ├── ocean0.jpg

│ │ ├── ocean1.jpg

│ │ ├── ocean2.jpg

│ │ ├── ocean3.jpg

│ │ ├── timeDependentSpectrum.wgsl

│ │ └── wavesTexturesMerger.wgsl

│ ├── createScene.ts

│ ├── externalFileTypes.d.ts

│ ├── index.html

│ ├── index.ts

│ └── scenes

│ ├── buoyancy.ts

│ ├── fft.ts

│ ├── initialSpectrum.ts

│ ├── ocean.ts

│ ├── oceanGeometry.ts

│ ├── oceanGui.ts

│ ├── oceanMaterial.ts

│ ├── skyBox.ts

│ ├── tools

│ │ ├── computeHelper.ts

│ │ ├── exrSerializer.ts

│ │ ├── math.vector.float32.ts

│ │ ├── RTTDebug.ts

│ │ └── skyMaterialExt.ts

│ ├── wavesCascade.ts

│ ├── wavesGenerator.ts

│ └── wavesSettings.ts

├── tsconfig.json

└── webpack.config.js

第一步:入口与架构(搞清楚它是怎么跑起来的)

先看外壳,弄明白这些 TypeScript 文件是怎么组织到一起的。

  1. src/index.ts :这是整个应用的"点火装置"。你已经看过了,它负责初始化 WebGPUEngine

  2. src/createScene.ts :这是一个分发器。这个 Demo 显然支持不同的场景(比如纯海洋、浮力模拟等),它通过 URL 参数决定加载哪个模块。

  3. src/scenes/ocean.ts这是你最该仔细读的文件。 它把天空盒、船只模型、海洋几何体以及最重要的"波动生成器"串联在一起。

第二步:核心逻辑(学习物理是怎么变成画面的)

这是项目的灵魂,建议按以下逻辑链路去读:

  1. src/scenes/wavesSettings.ts:看这里。了解风速、海浪强度等参数是如何定义的。

  2. src/scenes/wavesGenerator.ts重头戏! 这里负责调度 GPU 计算。它会调用 computeHelper.ts 来运行那些 .wgsl(计算着色器)。

  3. src/scenes/oceanMaterial.ts :这里定义了你看到的海洋"长什么样"。它负责处理光照、折射、反射以及读取 GPU 计算出来的位移贴图

第三步:硬核算法(进阶深水区)

如果你想弄明白为什么这个海浪这么真实,就得看 src/assets/ocean/ 下的 .wgsl 文件。这是 Web 开发者最高阶的技能之一。

  • initialSpectrum.wgsl:生成初始的海浪频谱(通常基于 Phillips 频谱公式)。

  • fftInverseFFT.wgsl核心算法。利用快速傅里叶逆变换,把频率域的数据转回我们肉眼能看到的波动(高度场)。

  • wavesTexturesMerger.wgsl :把计算好的位移、法线、泡沫数据合并成最终的贴图。

第四步: 学习路径

阶段 文件 目标
1. 跑通流程 index.ts -> ocean.ts 搞清楚一个 WebGPU 场景从创建到渲染的生命周期。
2. 操控 UI oceanGui.ts 修改代码,看看调节风速(Wind Speed)时,哪些变量被传给了着色器。
3. 材质魔法 oceanMaterial.ts 尝试修改 oceanMaterial 里的颜色或反射率,理解着色器是如何渲染水面的。
4. 物理模拟 wavesGenerator.ts 理解 ComputeShader 是如何每一帧都在后台更新海浪形状的。

第四步:程序运行路径

从你双击 npm run dev 到屏幕上出现第一朵浪花,拆解出一条清晰的运行路径

(1)初始化阶段:点火与引擎挂载

一切的起点在 src/index.ts

  • 浏览器握手 :程序首先通过 WebGPUEngine.IsSupportedAsync 检查你的浏览器(Canary 版本)是否支持 WebGPU。

  • 引擎启动 :创建一个 WebGPUEngine 实例,并配置 deviceDescriptor。这里非常关键,它申请了 timestamp-queryindirect-first-instance 等高级特性,这是跑计算着色器(Compute Shader)的前提。

  • 模块加载 :通过 getSceneModuleWithName 动态加载 src/scenes/ocean.ts(默认场景)。

(2)场景构建阶段:资源与逻辑组装

当程序进入 src/scenes/ocean.tscreateScene 函数时,它开始疯狂"搭积木":

A. 环境配置
  • 调用 skyBox.ts 生成天空盒。

  • 加载 src/assets/environment/qwantani_1k.hdr。这不只是背景,它为海洋提供了 IBL (基于图像的光照),决定了海水的环境反射。

B. 核心组件实例化(最关键的路径)

程序在这里初始化了两个重量级对象:

  1. WavesGenerator (src/scenes/wavesGenerator.ts):这是"大脑",负责所有物理计算。

  2. OceanMaterial (src/scenes/oceanMaterial.ts):这是"皮肤",负责最终的视觉呈现。

(3)渲染循环阶段:数据的"生死时速"

一旦执行 engine.runRenderLoop,每一帧(约 16ms)都会经历以下闭环:

步骤一:频谱生成(Initial & Time-Dependent)
  • 文件路径initialSpectrum.ts -> initialSpectrum.wgsl

  • 逻辑:GPU 根据风速、海浪强度(Settings),在频率域生成初始的海浪分布。它不是生成形状,而是生成"波的能量密度"。

步骤二:逆傅里叶变换(IFFT)
  • 文件路径wavesGenerator.ts -> fftInverseFFT.wgsl

  • 逻辑 :这是项目最硬核的地方。GPU 将频率域的数据通过 IFFT 转换回空间域。

  • 产出 :生成几张关键的 RTT (渲染目标贴图)

    • Displacement Map:告诉顶点网格往哪里跳(高度和水平位移)。

    • Normal Map:决定光的折射和反射角度。

    • Foam Map:计算波浪破碎处的泡沫。

步骤三:材质着色(Fragment Shading)
  • 文件路径oceanMaterial.ts

  • 逻辑

    1. 顶点偏移 :读取 Displacement Map,把平整的 oceanGeometry 顶起来,变成起伏的海浪。

    2. 像素着色 :结合天空盒反射、HDR 环境光,以及 mix-blend-mode: screen 等逻辑,计算出深蓝到浅绿的渐变。

(4) 交互阶段:反馈与浮力

  • buoyancy.ts :如果你在场景里放了那只鸭子或小船(.glb 模型),程序会实时采样海浪的高度数据。

  • 逻辑 :它读取当前时刻坐标下的海浪高度,通过 buoyancy.ts 计算浮力,实时修正模型的 positionrotation,让船看起来真的漂在水上。

相关推荐
Highcharts.js2 小时前
Highcharts 使用指南Treegraph chart 树状图/结构树图|创建谱系图表、决策树、结构知识树等的图表工具
javascript·决策树·highcharts·图表开发·结构树·可视化图表库·谱系图表
局i2 小时前
React 简单地图组件封装:基于高德地图 API 的实践(附源码)
前端·javascript·react.js
进击的尘埃2 小时前
Service Worker + stale-while-revalidate:让页面"假装"秒开的正经方案
javascript
yuki_uix2 小时前
防抖(Debounce):从用户体验到手写实现
前端·javascript
张元清2 小时前
每个 React 开发者都需要的 10 个浏览器 API Hooks
前端·javascript·面试
进击的尘埃2 小时前
给 Claude Code 造个趁手的 MCP Tool Server,聊聊我踩的那些坑
javascript
yuki_uix2 小时前
深拷贝:JavaScript 引用类型的完全复制之道
前端·javascript
默默学前端3 小时前
JavaScript 中 call、apply、bind 的区别
开发语言·前端·javascript
℘团子এ3 小时前
vue3中,el-table表格固定列后出现表格线段折断的问题
javascript·vue.js·elementui