跳到主要内容
3D Playground
GPU 与 WebGPU 计算第 7 / 11 节

WebGL2 上的 GPGPU:水波模拟

高级WebGL 2

没有计算着色器怎么办

WebGL 没有 compute。早期的 GPU 通用计算(GPGPU)用的是一个巧妙的替代:

  • 数据存在浮点纹理里,一个纹素就是一个数据单元;
  • 计算写在片元着色器里,向一张同尺寸的渲染目标画一个全屏四边形,每个纹素各跑一次;
  • 下一步再把刚写的纹理当输入,两张纹理轮流读写(乒乓)。

这里的模拟网格是 256×256 的 RGBA 浮点纹理:r 是当前高度,g 是上一步的高度。three.js 的 GPUComputationRenderer 帮我们管理渲染目标和交换。

波动方程

离散的波动方程只需要上下左右四个邻居:

glsl
float next = ((n + s + e + w) * 0.5 - h.g) * damping;
gl_FragColor = vec4(next, h.r, 0.0, 1.0);

下一刻的高度等于邻居平均的两倍减去上一刻的高度,再乘以阻尼让波慢慢平息。把当前高度写进 g,下一步就成了“上一刻”。

把数据变成画面

水面是一个 256×256 细分的平面。顶点着色器从高度纹理采样,把顶点抬高,并用相邻纹素的有限差分算出法线:

glsl
float dx = H(uv + vec2(texel, 0.0)) - H(uv - vec2(texel, 0.0));
vNormal = normalize(vec3(-dx / step, 1.0, dy / step));

片元着色器用菲涅尔项混合水体颜色和程序化天空的反射,再加一点高光。

交互

鼠标射线与水平面求交,得到 uv 坐标,作为“水滴”的位置传给模拟着色器;没有鼠标时随机下雨。每帧可以跑多步模拟(sim steps / frame),波传播得更快。

与 WebGPU 计算的对比

这种方法到处都能用,但有局限:每个线程只能写自己那个纹素,不能随意写入任意位置,也没有工作组共享内存。WebGPU 的计算着色器和存储缓冲解除了这些限制,这正是本单元后面几课的主题。

核心 API

GPUComputationRenderergpgpu.addVariablegpgpu.setVariableDependenciesgpgpu.computegetCurrentRenderTargetTHREE.ShaderMaterialTHREE.Raycastertexture2D

动手试试

  1. 1把 damping 调到 0.999,波纹几乎不衰减,会发生什么?
  2. 2把 sim steps / frame 调到 6,对比波的传播速度。
  3. 3把邻居的权重 0.5 改成 0.49,观察能量如何更快地流失。
运行中