{
  "lead": "与「粒子物理」相同的 N 体引力模拟，但物理计算和渲染都放到 GPU 上完成，可以实时模拟成千上万个粒子。Rust/WASM 只负责生成粒子的初始位置和速度。",
  "formulas": [
    [
      "工作组数 = ⌈粒子数 / 256⌉",
      "每个线程负责 1 个粒子"
    ]
  ],
  "terms": [
    [
      "计算着色器（Compute Shader）",
      "用 WGSL 编写。每个 GPU 线程负责一个粒子，遍历所有其他粒子累加引力，然后更新自己的速度和位置。每个工作组 256 个线程，每帧派发 <code>⌈粒子数 / 256⌉</code> 个工作组。"
    ],
    [
      "同样是 O(n²)",
      "算法复杂度和 CPU 版一样，但 GPU 有成百上千个核心同时计算，所以能处理的粒子数多出一两个数量级。"
    ],
    [
      "数据不回传",
      "粒子状态 <code>[x, y, vx, vy]</code> 始终保存在 GPU 的存储缓冲区里。顶点着色器直接按 <code>vertex_index</code> 从同一个缓冲区读取位置，以点的形式绘制，全程不回到 CPU。"
    ],
    [
      "加色混合",
      "渲染管线使用 <code>src-alpha + one</code> 的混合方式，粒子重叠的地方会叠加变亮，形成发光效果。"
    ],
    [
      "简化处理",
      "为了代码简洁，计算着色器直接原地读写同一个缓冲区，没有使用双缓冲。同一帧里有些粒子可能读到已经更新过的位置，这对视觉效果的影响可以忽略。"
    ]
  ],
  "flow": [
    [
      "js",
      "页面加载时逐项检测 WebGPU 环境（API、浏览器版本、适配器、设备、Canvas 上下文），全部通过才开始运行。"
    ],
    [
      "rust",
      "JS 调用 Rust 的 <code>gen_gpu_particles(数量, 900, 600)</code>，得到扁平的 <code>Float32</code> 初始数据，一次性上传到 GPU 缓冲区。"
    ],
    [
      "gpu",
      "每帧 JS 只把引力等参数写入一个 28 字节的 uniform 缓冲区，然后提交一个计算通道和一个渲染通道，其余工作全部由 GPU 完成。"
    ]
  ],
  "perf": [
    "CPU 与 GPU 之间每帧几乎没有数据传输，这是 GPU 计算能获得高性能的关键。",
    "可以把粒子数量调到 20,000，再与 <a href=\"../particles/\">CPU 粒子物理</a>（300 个粒子）对比两者的差距。",
    "需要 Chrome / Edge 113+ 和可用的 GPU。虚拟机或没有硬件加速的环境会显示检测失败的原因。"
  ],
  "src": [
    [
      "crates/simulation/webgpu/src/lib.rs",
      ""
    ],
    [
      "www/webgpu/index.js",
      "内含 WGSL 着色器"
    ]
  ]
}
