
背景知识参考:
http://www.karlsims.com/rd.html
https://www.lanevol.org/resources/gray-scott
今天我们要学习的是反应扩散图案的制作,关于该图案的背景知识可参考上面两个网站。下面列举了几个不同参数下得到的结果——

Reaction Diffusion Patterns
关于这个图案的程序生成思路可以参考油管上的Coding Challenges系列视频,只不过视频里用的是Java,我用的是Houdini VEX而已。
其实过程的描述很简单,如下图所示:体系中有两种物质A和B,扩散速度分别为dA和dB。两个B可以将一个A转化为B,同时物质A以速率“feed”加入,物质B以速率“kill”消失。最终显示的是物质A在体系中的分布。

www.karlsims.com/rd.html
A和B的浓度范围为0~1。为了简便起见,最后的可视化可以选择在一个平面中呈现。用不同的颜色来表示物质的浓度。初始创建一个grid节点(Size:1×1;Rows/Columns:250),给每个point设置属性A和属性B,A的初始值设为1,B的初始值设为0。

添加Point Wrangle节点,VEX代码如下:

下一步是往体系中随机添加一些B物质,使用的是Attribute Wrangle节点,Run Over设置为Detail (only once)。

VEX代码如下:
float maxdist = chf("maxdist");
for(int i = 0; i < chi("counts"); i++){
vector rd = rand(i + chf("seed"));
vector coordinate = {-0.5, 0, -0.5} + set(rd[0], 0, rd[2]);
int nPts[] = nearpoints(0, coordinate, maxdist);
foreach(int nPt; nPts){
vector pos = point(0, "P", nPt);
float dis = distance(pos, coordinate);
dis = fit(dis, 0, maxdist, 1, 0);
setpointattrib(0, "B", nPt, dis);
}
} 利用随机数生成点,用nearpoints函数搜索一定距离(maxdist)范围内的点,根据距离设置属性B的值。对于每个生成的随机点,距离为0的点B值设为1,距离为maxdist的点B值设为0。若按照属性B的值添加颜色显示,可得到如下所示的斑点图案,从斑点中心到边缘,B值从1渐变到0。

初始设置完成后,接下来就是反应扩散的过程了。创建solver节点,里面还是用point wrangle节点来实现每一步属性A和B值的变化。变化按照下面的公式进行,A和B是当前的值,dA、dB、f、k以及δt都是自定义的参数。唯一稍难理解的是倒三角符号,这是一个Laplace算符,用来表示浓度梯度的变化。

其实,这里的Laplace算符计算方式很简单。在grid平面中,除了边缘的点,每个点都会被另外8个点包围。这些点都有属性A和B。计算新的属性值时,只要将周围点对应的属性值乘上某个系数,然后相加即可。相邻的点乘以系数0.2,对角的点乘以0.05,自身乘以-1。

这里我们有两个属性值要计算,可以自定义一个laplace函数,最后返回A和B两个值。先设置几个基本参数如下:

laplace函数返回的数据类型为vector2,步骤说明如下:
vector2 laplace(int npt){
//找到上下左右周围的八个点编号
//获取每个点的A或B属性值
//乘上相应的系数并求和
} 由点编号可知,对于npt号点,左右分别是npt-1和npt+1,上下分别是npt-250和npt+250(250是这里的网格细分数,为方便起见,这里我用的是固定数值,如果要设为可调节的值,这里的代码也应相应做出调整)。还有四个点的编号分别为npt-251,npt-249,npt+249,npt+251。

显而易见的是,边缘的点不满足上述关系。为了不麻烦,我们只对非边缘的点执行laplace函数计算。
先写出laplace函数:
vector2 laplace(int npt){
//找到上下左右周围的八个点编号
int ptid[] = array(0, -1, 1, -250, 250, -251, -249, 249, 251);
float weight[] = array(-1, 0.2, 0.2, 0.2, 0.2, 0.05, 0.05, 0.05, 0.05);
float sumA = 0;
float sumB = 0;
//获取每个点的A或B属性值
//乘上相应的系数并求和
for(int i = 0; i < 9; i++){
sumA += point(0, "A", npt + ptid[i]) * weight[i];
sumB += point(0, "B", npt + ptid[i]) * weight[i];
}
vector2 laplace = set(sumA, sumB);
return laplace;
} 在解算之前添加Group节点,将边缘的点加到“boundary”组中。

用inpointgroup函数判断,只对不在组内的点进行laplace变换计算。公式如下:
if(inpointgroup(0, "boundary", @ptnum) == 0){
float na = @A + (dA * laplace(@ptnum)[0] - @A * @B * @B + f * (1 - @A)) * dt;
float nb = @B + (dB * laplace(@ptnum)[1] + @A * @B * @B - (k + f) * @B) * dt;
@A = na;
@B = nb;
} 最后添加color节点,用属性A作为着色依据。

最后增加solver节点的Sub Steps,播放就可以看到反应扩散图案的变化,如下图所示——

dA = 1.0; dB =0.45; f = 0.03; k = 0.062; dt = 1.0;
改变参数就可以得到不同的扩散图案,具体可以参考本片开头给出的网址。
今天的分享到此结束,感谢各位的阅读,下回见~