为心理治疗实践构建生产级 WebGPU 引擎...
Source: Dev.to
请提供您希望翻译的完整文本内容,我将按照要求保留源链接、格式和技术术语,仅翻译正文部分。
挑战:噪声化为流动
当为 Therapy Warsaw 打造数字形象时,我们遇到了一个不同寻常的需求。我们不想使用库存照片或静态插图。我们想要一种有生命感的东西——一种始终在变化但又不抢眼的生成式纹理。
视觉隐喻很简单:复杂的图案寻找清晰。噪声场慢慢自组织成连贯、流动的线条。
技术需求
- 有机且密集:约 10,000 个相互作用的粒子。
- 性能关键:在移动设备上滚动时保持 60 FPS。
- 兼容性强:必须在十年前的笔记本(WebGL2)和前沿设备(WebGPU)上均能运行。
- 无框架:不使用 React、Three.js,仅使用受控、流畅的逻辑。
架构:双栈 WebGPU + WebGL2 引擎
主线程外渲染
在网页上进行高负载图形渲染的第一条原则:脱离主线程。
| 线程 | 职责 |
|---|---|
| 主线程 | DOM、可访问性、路由、UI 状态 |
| 工作线程 | 物理、几何生成、通过 OffscreenCanvas 渲染 |
即使物理模拟出现卡顿,页面滚动仍然流畅。通信通过专用的消息系统进行,可同步视觉“预设”(颜色、速度、湍流),且不会阻塞。
// main.js
const worker = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
const offscreen = canvas.transferControlToOffscreen();
// Hand ownership to the worker
worker.postMessage({ type: 'init', canvas: offscreen }, [offscreen]);
WebGPU 实现
我们从 WebGPU 开始,因为 计算着色器 非常适合粒子系统。
Compute Passes
| 通道 | 目的 |
|---|---|
| 映射通道 | 生成噪声纹理(燃烧、密度、空洞图) |
| 流动通道 | 计算向量场 |
| 生命周期通道 | 更新粒子年龄并处理重置 |
| 物理通道 | 根据流向向量移动粒子 |
关键的性能提升在于:避免 CPU‑GPU 往返。整个模拟全部在 GPU 上运行。
使用 Transform Feedback 的 WebGL2 回退
WebGPU 的支持正在增长,但并非普遍,因此我们需要一种不会变成“笨拙”回退的方案。
- Transform Feedback 允许 WebGL2 在顶点着色器中更新粒子位置并将其写回缓冲区,模拟计算着色器。
- 这种方法在不让 CPU 负担过重的情况下保持功能等价。
平滑参数过渡:临界阻尼弹簧系统
当用户在页面之间切换时,可视化会发生形变(颜色变化、混沌改变、速度调整)。简单的线性插值显得机械化,于是我们实现了一个临界阻尼弹簧系统:
function updateSpring(state, target, dt) {
const tension = 120;
const friction = 20;
const displacement = target - state.value;
const force = tension * displacement - friction * state.velocity;
state.velocity += force * dt;
state.value += state.velocity * dt;
}
每帧我们更新约 20 个受弹簧驱动的参数,并将它们上传到 Uniform Buffer Object (UBO),从而产生一种更具物理感而非纯计算的过渡效果。
高效轨迹渲染
传统的粗线渲染方式需要为每段生成两个三角形(六个顶点)——对长轨迹来说开销很大。
我们的方法
- 只存储每条线的 头部位置。
- 在顶点着色器中运行一个循环(约 60 次),向后追踪路径,通过流场实时重建轨迹。
优点:带宽大幅降低(每条线只需 1 个点,而不是成千上万个顶点)。
缺点:每个顶点的 ALU 开销更高。
在现代 GPU 上,ALU 成本低廉,而带宽昂贵。此权衡使我们能够在移动设备上渲染成千上万条长且平滑的轨迹。
Result
最终站点 therapywarsaw.com 采用了活跃的背景——一种安静的纹理,既体现了工作的本质,又在各种设备上保持良好的性能。
开源
该引擎是开源的:
github.com/23x2/generative-flow-field
欢迎探索着色器管线或 Transform Feedback 实现。