YBGAMER_
  • □字体FONTS...
  • □资源ASSETS...
000%

在 GPU 上跑生命游戏:从 4 条规则到 100 万个细胞

生命游戏只有四条规则,却能长出滑翔机、振荡器甚至图灵机。这篇文章把它搬到 GPU 上,顺便聊聊"规则"与"结果"之间那段说不清的距离。

DATE2026-10-28
READ3 min
VIEWS8
CERTAINTY 较确定 · 85%

生命游戏(Conway’s Game of Life)是一个零玩家游戏:你只决定初始状态,之后的一切都由规则推演。规则只有四条:

  1. 活细胞周围少于 2 个活邻居,死于孤独;
  2. 活细胞周围有 2 或 3 个活邻居,继续存活;
  3. 活细胞周围多于 3 个活邻居,死于拥挤;
  4. 死细胞周围恰好 3 个活邻居,诞生。

就这些。但从这四条规则里,可以长出会移动的滑翔机、周期性的振荡器、无限生长的枪,甚至能搭出一台图灵完备的计算机。

上面这个就是实时运行的版本——用鼠标在上面划一划,你就在给这个世界"播种"。

# 为什么要搬到 GPU 上

我在 2020 年用 Canvas 2D 写过一版,逐格遍历、逐格绘制。在 200×200 的网格上还挺流畅,但网格一大就开始掉帧:每一帧都要在 CPU 上做 宽 × 高 × 8 次邻居查询。

而生命游戏有一个非常适合 GPU 的特性:每个细胞的下一状态只依赖于上一帧的邻居,彼此之间完全独立。这正是片元着色器擅长的事情。

# 乒乓缓冲

思路是准备两张纹理 A、B:

  • 读 A,把下一代写进 B;
  • 下一帧读 B,写进 A;
  • 如此往复。

这就是所谓的 ping-pong。每个像素就是一个细胞,R 通道存活/死亡,G 通道存一个衰减的"余温",用来画出拖尾。

# 核心着色器

glsl
// simulate.frag — one invocation per cell
uniform sampler2D uState;
uniform vec2 uSize;

float alive(vec2 offset) {
  vec2 uv = (gl_FragCoord.xy + offset) / uSize;
  return texture(uState, fract(uv)).r; // fract() wraps the edges
}

void main() {
  float n = 0.0;
  for (int y = -1; y <= 1; y++)
    for (int x = -1; x <= 1; x++)
      if (x != 0 || y != 0) n += alive(vec2(x, y));

  vec4 self = texture(uState, gl_FragCoord.xy / uSize);
  float next = (n == 3.0 || (self.r > 0.5 && n == 2.0)) ? 1.0 : 0.0;
  float trail = max(next, self.g * 0.94);
  outColor = vec4(next, trail, 0.0, 1.0);
}

整个"世界的物理定律"就写在 next 那一行里。剩下的都是工程。

一个小细节

fract(uv) 让边界环绕起来,世界变成一个甜甜圈形状的环面。没有边界,滑翔机就能永远飞下去。

# 在 JS 这一侧

ts
function step() {
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[next])
  gl.useProgram(simProgram)
  gl.bindTexture(gl.TEXTURE_2D, tex[current])
  gl.drawArrays(gl.TRIANGLES, 0, 3) // one fullscreen triangle
  ;[current, next] = [next, current]
}

一个覆盖全屏的三角形,就能让 GPU 对每个细胞并行地跑一遍规则。1024×1024 的网格——一百多万个细胞——在集成显卡上也能稳定 60 帧。

# 规则与结果之间的距离

做完这个之后我一直在想一个问题:我写下了全部的规则,但我完全预测不了它会长成什么样。

知道一个系统的全部规则,并不等于理解这个系统。

这对做游戏的人来说是个很有意思的处境。我们设计的从来不是"玩家会经历什么",而是"让什么事情有可能发生"。关于这一点,我在《涌现不是设计出来的,但可以被设计》里展开得更多。

还没想明白的

如果在规则里加入一点点随机性——比如每个细胞有 0.1% 的概率自发诞生——系统会走向混沌,还是会出现新的稳定结构?

# 小结

方案 网格规模 帧率
Canvas 2D 200 × 200 60
Canvas 2D 1000 × 1000 4
WebGL 乒乓 1024 × 1024 60

规则本身一行没变,变的只是执行它的方式。这个网站首页的背景,就是这套代码在跑。

讨论 0

/

还没有评论。第一个说点什么吧。