Hotdry.

Article

使用 WebGPU 计算着色器并行化元胞自动机规则

利用 WebGPU 计算着色器在 GPU 线程上并行执行元胞自动机规则,实现浏览器环境中大网格实时模拟,CPU 开销最小化。

2025-10-10application-security

元胞自动机(Cellular Automata, CA)是一种通过简单局部规则产生复杂全局行为的计算模型,如著名的康威生命游戏(Conway's Game of Life)。传统 CPU 实现中,每个细胞的邻域计算需串行执行,导致大规模网格模拟效率低下。而在浏览器环境中,利用 WebGPU 计算着色器可以将这些规则并行化到数千个 GPU 线程上,实现实时高分辨率模拟,同时将 CPU 开销降至最低。这种方法特别适合交互式 Web 应用,如教育工具或生成艺术。

WebGPU 作为下一代 Web 图形 API,提供计算着色器(Compute Shaders)支持通用并行计算。核心观点是:每个 GPU 线程独立处理一个细胞的状态更新,避免了 CPU 的瓶颈。通过 WGSL(WebGPU Shading Language)编写着色器,可以高效采样邻域并应用规则。证据显示,对于 512x512 网格,GPU 并行化可将每帧计算时间从 CPU 的数百毫秒缩短至微秒级,支持 60 FPS 实时渲染。这不仅提升了性能,还降低了功耗,尤其在移动设备上。

实施康威生命游戏作为起点:使用两个存储缓冲区(Storage Buffers)实现 ping-pong 机制,一个读当前状态,一个写下一代状态。缓冲区大小为网格分辨率乘以元素大小(例如 Uint32Array 为 4 字节 / 细胞)。初始化时,在 CPU 上生成随机噪声并上传至 GPU:

const boardSize = 512;
const bufferSize = boardSize * boardSize * 4;
const buffers = [
  device.createBuffer({ size: bufferSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST }),
  device.createBuffer({ size: bufferSize, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST })
];
const initialState = new Uint32Array(boardSize * boardSize);
for (let i = 0; i < initialState.length; i++) {
  initialState[i] = Math.random() > 0.5 ? 1 : 0;
}
device.queue.writeBuffer(buffers[0], 0, initialState);

着色器中,定义工作组大小为 8x8 以优化 GPU 利用率:

@group(0) @binding(0) var<storage, read> readBuffer: array<u32>;
@group(0) @binding(1) var<storage, read_write> writeBuffer: array<u32>;
const boardSize: u32 = 512u;

@compute @workgroup_size(8, 8, 1)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
  let idx = id.y * boardSize + id.x;
  var neighborCount: u32 = 0u;
  for (var dy: i32 = -1; dy <= 1; dy++) {
    for (var dx: i32 = -1; dx <= 1; dx++) {
      if (dx == 0 && dy == 0) { continue; }
      var nx = i32(id.x) + dx; var ny = i32(id.y) + dy;
      nx = select(nx, nx + i32(boardSize), nx < 0);
      ny = select(ny, ny + i32(boardSize), ny < 0);
      nx = select(nx, nx - i32(boardSize), nx >= i32(boardSize));
      ny = select(ny, ny - i32(boardSize), ny >= i32(boardSize));
      if (readBuffer[ny * boardSize + nx] == 1u) { neighborCount++; }
    }
  }
  let alive = readBuffer[idx];
  var nextState: u32 = 0u;
  if (neighborCount == 3u || (alive == 1u && neighborCount == 2u)) {
    nextState = 1u;
  }
  writeBuffer[idx] = nextState;
}

渲染时,使用片元着色器将细胞状态映射为黑白像素,支持后处理效果如模糊或颜色渐变。绑定组交替使用以切换读写缓冲区,每帧 dispatchWorkgroups (boardSize / 8, boardSize / 8, 1)。

扩展到更复杂变体:对于循环元胞自动机(Cyclic CA),使用多状态(例如 7 状态),每个细胞寻找下一状态的邻域计数,阈值设为 6 以产生稳定图案。连续元胞自动机(Continuous CA)如 Primordia,使用 Float32Array 缓冲区,计算邻域平均密度(Moore 邻域除以 8),增长阈值 [0.2, 0.25]、衰减阈值 [0.19, 0.334],增量 delta = 1/32 以平滑过渡。

多邻域 CA(Multiple Neighborhood CA)结合内 Moore 邻域(范围 1)和外圆形邻域(最小距离 3,最大 5),分别计算存活百分比,规则如内域 [0.5,1.0] 激活、外域 [0.281,0.301] 出生。风险包括 GPU 内存溢出(大网格超 1GB),建议从 256x256 开始测试;浏览器兼容性(Chrome 113+)需 fallback 到 WebGL。

可落地参数与清单:

  1. 网格配置:分辨率 256-1024(偶数,便于 workgroup 分割);边界条件:环绕(toroidal)以避免边缘伪影。

  2. 性能优化:工作组大小 8x8 或 16x16,根据 GPU;使用 storage buffers 而非 uniform 以支持大数组;监控 GPU 使用率 via chrome://gpu。

  3. 规则参数:生命游戏 B3/S23;循环 CA 状态数 4-8,阈值 邻域大小的 20-30%;连续 CA delta 0.01-0.05,避免振荡。

  4. 渲染与交互:全屏四边形顶点;支持鼠标注入初始图案;帧率目标 60 FPS,超时 >100ms 降分辨率。

  5. 回滚策略:若 WebGPU 不可用,回退 CPU 模拟(限 128x128);错误处理:检查 adapter.requestAdapter () 失败时提示更新浏览器。

通过这些实践,开发者可在 Web 端构建高效 CA 模拟器,探索 emergent behavior 的魅力,同时保持低开销与高交互性。(字数约 1250)

application-security