在 GPU 上跑生命游戏:从 4 条规则到 100 万个细胞
生命游戏只有四条规则,却能长出滑翔机、振荡器甚至图灵机。这篇文章把它搬到 GPU 上,顺便聊聊"规则"与"结果"之间那段说不清的距离。
生命游戏(Conway’s Game of Life)是一个零玩家游戏:你只决定初始状态,之后的一切都由规则推演。规则只有四条:
- 活细胞周围少于 2 个活邻居,死于孤独;
- 活细胞周围有 2 或 3 个活邻居,继续存活;
- 活细胞周围多于 3 个活邻居,死于拥挤;
- 死细胞周围恰好 3 个活邻居,诞生。
就这些。但从这四条规则里,可以长出会移动的滑翔机、周期性的振荡器、无限生长的枪,甚至能搭出一台图灵完备的计算机。
上面这个就是实时运行的版本——用鼠标在上面划一划,你就在给这个世界"播种"。
# 为什么要搬到 GPU 上
我在 2020 年用 Canvas 2D 写过一版,逐格遍历、逐格绘制。在 200×200 的网格上还挺流畅,但网格一大就开始掉帧:每一帧都要在 CPU 上做 宽 × 高 × 8 次邻居查询。
而生命游戏有一个非常适合 GPU 的特性:每个细胞的下一状态只依赖于上一帧的邻居,彼此之间完全独立。这正是片元着色器擅长的事情。
# 乒乓缓冲
思路是准备两张纹理 A、B:
- 读 A,把下一代写进 B;
- 下一帧读 B,写进 A;
- 如此往复。
这就是所谓的 ping-pong。每个像素就是一个细胞,R 通道存活/死亡,G 通道存一个衰减的"余温",用来画出拖尾。
# 核心着色器
// 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 这一侧
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
登录后参与讨论
登录 / 注册还没有评论。第一个说点什么吧。