卧槽!GPU里的生命游戏,居然还能这样玩!
共 2949字,需浏览 6分钟
·
2021-05-30 06:07
简单,优雅,有极强的涌现性,又发人深省。这就是能令我盯着它发呆的"生命游戏"。今天练习在 GPU 里运行"生命游戏",文末有项目地址。
生命游戏的规则
生命游戏(Game of Life)是一类二维的元胞自动机,由 J.Conway 在1970年代设计。规则如下:
有一个二维网格,每个格子代表一个元胞。 格子有0和1两种状态,对应元胞的"死"和"生"。 每个元胞有8个相邻的元胞,元胞和其8个邻居的当前时刻状态决定了它下一时刻的状态。 如果元胞当前为"生",则仅当8个邻居中有2个或3个为"生"时,该元胞保持"生",否则变为"死" 如果元胞当前为"死",则仅当8个邻居中有3个为"生"时,该元胞变为"生",否则保持"死"
在GPU里运算
生命游戏的规则很简单,并且对并行计算非常友好,非常适合通过 GPU 进行运算和展现。本文使用 Cocos Creator 2.4.0,通过编写shader实现基于 GPU 的生命游戏运算。
流程
整个过程涉及到3张纹理:表示网格初始状态的纹理T
和两个 RenderTexture (分别命名为RTA
、RTB
)。 RTA
和RTB
会在运行时交替地计算自身的下一时刻状态到对方纹理中。大致流程如下:
设置纹理状态
// 禁用纹理动态合图
texture.packable = false;
// 采样坐标周期循环,可以比较方便地实现元胞自动机的周期型边界条件,不过要求纹理大小必须是2的幂
texture.setWrapMode(cc.Texture2D.WrapMode.REPEAT, cc.Texture2D.WrapMode.REPEAT);
// 使用最近距离采样,避免出现插值
texture.setFilters(cc.Texture2D.Filter.NEAREST, cc.Texture2D.Filter.NEAREST);
状态迭代
根据规则,需要对当前位置和周围8个邻居的状态进行采样。对邻近位置进行采样需要根据纹素大小进行偏移,这里命名为dx
和dy
,通过 uniform 变量传入 shader。 dx=1.0/width
,dy=1.0/height
。
// 统计8个邻居的状态,计算结束后sum的范围是[0.0, 8.0]
vec4 sum = vec4(0.);
vec4 d = vec4(dx, dy, -dy, 0.);
sum += texture(texture, uv-d.xy); // 即uv + (-dx, -dy)处采样
sum += texture(texture, uv-d.xw); // 即uv + (-dx, 0.)处采样
sum += texture(texture, uv-d.xz); // 即uv + (-dx, +dy)处采样
sum += texture(texture, uv+d.wz); // ...以此类推
sum += texture(texture, uv+d.wy);
sum += texture(texture, uv+d.xz);
sum += texture(texture, uv+d.xw);
sum += texture(texture, uv+d.xy);
判断sum
是否在某个区间内,可以将两个step()
函数叠加进行判断。
// 如果元胞当前为"生",则仅当8个邻居中有2个或3个为"生"时,该元胞保持"生",否则变为"死"
// 只有当sum = 2或者3时,oneCase的值是1.0
vec4 oneCase = step(vec4(1.9), sum) * step(sum, vec4(3.1));
// 如果元胞当前为"死",则仅当8个邻居中有3个为"生"时,该元胞变为"生",否则保持"死"
// 只有当sum = 3时,zeroCase的值是1.0
vec4 zeroCase = step(vec4(2.9), sum) * step(sum, vec4(3.1));
根据当前元胞自身状态进行分支选择
vec4 col = texture(texture, uv);
col = mix(zeroCase, oneCase, col);
初始状态
不同的初始状态决定了生命游戏的后续发展,通过精心设计初始状态,可以产生状态循环、整体平移等神奇的表现。
这里直接搬运网络上的现成的模型翻译成RenderTexture
作为初始状态,参考了 https://funnyjs.com/jspages/game-of-life.html
并行的生命游戏
表示元胞的"生"、"死"不需要用完一个vec4
变量,只需要一个分量即可。所以当我们把初始状态纹理的 RGB 通道解耦,让它们独立取0或1后,我们就得到了3个生命游戏的初始状态纹理,shader 代码不需要改动就可以支持3个生命游戏的并行演算(A通道也利用上就是4个)。
代码 & Demo
源码地址:
http://store.cocos.com/app/detail/2895 (点击阅读原文可跳转)
参考
https://zhuanlan.zhihu.com/p/39451507
http://micro.ustc.edu.cn/CompPhy/lecturenote/comp_sun_3_4.pdf
https://funnyjs.com/jspages/game-of-life.html