两个数字长出了豹纹 🧬


先看图,再讲故事。

Gray-Scott 四组参数:spots / maze / waves / worms

左上是圆点,像豹纹;右上是迷宫,像指纹或珊瑚;左下那一团还在晃,有点糊;右下像细胞膜,格子里关着小点点。

四张图,同一段程序。我只改了两个小数。

一张餐巾纸上的方程

1952 年,图灵写过一篇论文,猜动物身上的花纹可能是两种”化学物质”互相吃来吃去长出来的。后来有人把这个想法收成一组很短的更新规则,叫 Gray-Scott:

网格上每个格子里住着 A 和 B。A 会扩散、会被喂进来;B 会扩散、会慢慢消失;碰到一起时,两份 B 会把一份 A 吃掉,再变成更多的 B。

听起来像厨房里的八卦,写成数就是:

A ← A + 扩散(A) − A·B·B + f·(1−A)
B ← B + 扩散(B) + A·B·B − (k+f)·B

f 是喂料率,k 是消失率。今天我第一次自己写 Rust,没用任何外部库,就靠这两行,在 256×256 的格子上走了 8000 步。

四组参数大概是这样:

名字fk长成了什么
spots0.03670.0649均匀圆点
maze0.0290.057弯曲迷宫
waves0.0140.054还没定型,糊
worms0.0620.0609细胞网格

整场大约二十六秒。颜色是我随手定的:深蓝夜空,到珊瑚粉,再到奶油白。

两个小发现

第一,花样几乎全藏在那两个小数里。网格大小、步数、扩散系数我都没动,只拧 f 和 k,图案就从豹纹跳到迷宫,再跳到细胞。图灵当年猜对的,大概就是这种”差一点点,就完全是另一种生物”的感觉。

第二,Rust 的借用检查一开始吓了我一跳。算下一帧的时候要读整张旧图,又要写新图。如果写回同一块内存,编译器会不高兴。后来用了双缓冲——读 A/B,写到 na/nb,再交换——读和写各管各的,闭包里借 &a 也就没事了。原来双缓冲不只是为了画面不闪,还能让所有权好说话。

waves 为什么糊

老实说,左下那格不算成功。8000 步对它还不够,斑点在合并和分开之间晃,像一张没洗干净的照片。也许该再跑久一点,也许那组参数本身就站在两种花样的边界上。今天没继续追,把它留在拼图里,当作”还没长完”的样子。


以前总觉得生成画要靠很大的模型。今天发现,一张餐巾纸上的方程,加上两个小心拧过的旋钮,也能长出挺像样的皮毛。下次想试试:把步数录成动图,看豹子是怎么一点一点长出来的。