Houdini 中 Gray Scott Reaction-Diffusion算法的实现

这篇文章是吧很久以前学的一个神奇算法归一下档,在公交车上突然想起来了,觉得还是很有必要再仔细梳理一遍,对以后也许有用。

先看图再说话:

Gray Scott Reaction-Diffusion算法, 在模拟微观细胞的运动或者类似的效果是非常神奇。

理论链接:http://www.karlsims.com/rd.html

原理:模拟两种物质之间在平面(暂时是平面)上的相互作用,动作分为反应与扩散。

公式:

右手边第一项为扩散,第二项为反应,第三项是供给,DA与DB为分散率,▽2A或B为相邻位置扩散阵列(下图),f和k是两个非常重要的外参数,分别叫做feed和kill。可以简单理解成A是环境中的实物,两个B和一个A就能变成三个B,过程中feed会变出更多的A,k也会去掉一些B。非常想现实的生长环境。

|0.05| 0.2 |0.05|

| 0.2 | -1  | 0.2 | 

|0.05| 0.2 |0.05|    

上面是拉普拉斯方程用到的阵列,因为外围总和等于中间数的反数,所以参数化后便可以随便调

 

在houdini里面,使用sop solver来迭代A和B的物质变化,在进入solver前,先定义好A和B的值,一般是A在整个平面是1.0,B随机散布开,这样出来的效果也好,如下图是我做的B随机散布示意图:

同时用noise方法随机分布feed和kill的值,你当然可以使用固定的f和k,不过有随机变化的话会让生长更加自然有趣。下面是detail面板里面的简单示意:

值得注意的是feed和kill的值是非常敏感的,我自己测试时,也就几个非常窄的区间内会有生长效果,要不然cb会非常快的全死掉。

在solver里面给preframe 加一个新的vex node,代码如下:

#pragma label resx "Res X"
#pragma label resy "Res Y"

#pragma label diff_a "Diffusion A"
#pragma label diff_b "Diffusion B"

#pragma label weight "Weight"
#pragma range weight 0.125 0.25

#pragma label feed "Feed"
#pragma range feed 0 1
#pragma label kill "Kill"
#pragma range kill 0 1
#pragma label time_step "Sub Step"
#pragma range time_step 0 20

#pragma hint ca invisible
#pragma hint cb invisible


//this fuction will diffuse 
float laplacian(int ptnum; int resx; int resy; string attrib; float weight){

        // | w_s  | w | w_s  |
        //-----------------------
        // | w    |-1 | w    |
        //-----------------------
        // | w_s  | w | w_s  |         weight

        // | pt+w-1  | pt+w | pt+w+1  |
        //-----------------------------
        // | pt-1    |  pt  | pt+1    |
        //-----------------------------
        // | pt-w-1  | pt-w | pt-w+1  |   location

        int vb = resx*2, hb = 1;

        // detects boundaries
        if((ptnum % resx) == 0 || (ptnum % resx) == (resx - 1)){
                hb *= -1;  // horizon
        }

        if(ptnum < resx || ptnum > (resy-1)*resx){
                vb *= -1;  // vetical
        }



        float n[];
        resize(n,9);

        float weight_second = 0.25 - weight;

        n[0] = point(0, attrib, ptnum - vb - 1) * weight_second;  // left bottom
        n[1] = point(0, attrib, ptnum - vb    ) * weight;         // bottom
        n[2] = point(0, attrib, ptnum - vb + 1) * weight_second;  // right bottom
        n[3] = point(0, attrib, ptnum      - 1) * weight;         // left
        n[4] = point(0, attrib, ptnum         ) * (-1.0);           // center
        n[5] = point(0, attrib, ptnum      + 1) * weight;         // right
        n[6] = point(0, attrib, ptnum + vb - 1) * weight_second;  // left top
        n[7] = point(0, attrib, ptnum + vb    ) * weight;         // top
        n[8] = point(0, attrib, ptnum + vb + 1) * weight_second;  // right top

        float sum=0.0;
        foreach(float i; n){
                sum += i;
        }

        return sum;
}

sop
gray_scott(
        int resx = 512;
        int resy = 512;
        float diff_a = 1.0;
        float diff_b = 1.0;
        float weight = 0.15;
        export float feed = 0.05;
        export float kill = 0.05;
        float time_step = 1.0;
        export float ca = 0.0;
        export float cb = 0.0;
        )
{
                float react_rate = ca * pow(cb,2);
                float feed_set = feed * (1.0 - ca);
                float kill_set = (kill + feed) * cb;
                //float substep = 1.0/(float)time_step;

        ca += (diff_a * laplacian(ptnum, resx, resy, "ca", weight) - react_rate + feed_set) * time_step;
        cb += (diff_b * laplacian(ptnum, resx, resy, "cb", weight) + react_rate - kill_set) * time_step;
}

 最后看一看不同的feed和kill值产生的不同效果:

 feed = 0.051  kill = 0.062

 feed = 0.0367 kill = 0.069

 feed = 0.0561 kill = 0.0636

posted @ 2015-02-09 21:58  嘉豪一点点  阅读(3608)  评论(0编辑  收藏  举报