叶脉图案以及藤蔓生长算法在houdini里面的实现 Leaf Venation
继续跟着CGworkshops里面的Shawn Lipowski大神学习在houdini里面写Vex,昨天简单看了一下他做植物生长的那一课,了解了一下思路于是开始自己着手写,本人不太习惯一步一步跟着学,一方面是觉得效率不高而且自己的思路很容易被视频打断从而很难在做出效果的同时也掌握它的核心,充其量就是一个照猫画虎的过程,画出了形却不知其意。写程序什么的思路最重要了不是么。
实现效果:
要自己写算法,当然一些相关的学术文章作参考是绝对少不了的,这个算法我直接拿的shawn用的那篇文章,但过程中因为我和他关于营养对种子的影响方式不同效果产生了一些不同,当然我还是觉得我自己的理解更牛逼一点,之后再详细讲这一点。这是参考论文链接:
http://algorithmicbotany.org/papers/venation.sig2005.pdf
整体来说,算法的核心是把对象分为种子Seed和营养Food/Auxin,种子在生长过程中查找一定半径范围R内的营养,根据营养所在位置确定种子的下一步生长位置,同时在半径范围r内的营养素将消耗干净,就是全部删掉,当然这个r是要小于R的,不然这个植物是怎么也生长不起来的。
下面是根据上面的那篇论文做的一些笔记,基本上涵盖了在houdini里面实现的步骤。
这里要补充的一点是关于根据营养位置确定下一步生长位置的两种不一样的算法实现,第一个是Shawn Lipowski用到的直接根据找到的所有营养的平均位置P_goal减去当前种子所在位置来确定生长方向矢量,但是这种方法太过于理想,我考虑到在实际过程中营养素所在的位置有贫瘠和肥沃之分,而且植物本能上是有趋利避害的行为本能的。所以我在确定生长方向时,增加了一个肥沃程度的权重给营养素。下图简单介绍两种方法的区别。
在Houdini里面的应用:
这是写在sop sovler里面的,其中写了两个point wrangle 和一个attrib wrangle。
closest_seed是来确定每个food最近的一个seed点。而这个seed点列表也能直接确定那些店是树梢顶端能够接着生长的点。
int handle;
int seed_ptnum;
handle = pcopen(1, "P", @P, 99999, 1);
while(pciterate(handle)){
pcimport(handle, "point.number", seed_ptnum);
i@seed = seed_ptnum;
}
treetop_difine主要用来确定seed点里面那些是树杈的顶端,其他的点在生长过程中就不做考虑,这样限定需要查询seed point cloud的条件能使查询量小很多。
int handle;
int seed_ptnum;
handle = pcopen(1, "P", @P, 9999, 1);
while(pciterate(handle)){
pcimport(handle, "seed", seed_ptnum);
if(@ptnum == seed_ptnum){
i@treetop = 1;
}else{
i@treetop = 0;
}
}
上面两个都是point wrangle节点,也是为生长做准备的一个步奏,接下来next_step_position就是生长中的重要一环,确定下一步生长点位置。
在确定好生长点的同时,增加原来的生长点到新点的连线,这样树枝就长出来了。
float food_radius = chf("radius");
int max_food = chi("neighbours");
float step_length= chf("step");
int prim;
if(@treetop == 1){
int handle;
int count_ptnum = 0;
handle = pcopen(1, "P", @P, food_radius, max_food);
if(pciterate(handle) != 0){
vector target_goal = set(0,0,0);
while(pciterate(handle)){
vector food_position;
float fertility;
pcimport(handle, "P", food_position);
pcimport(handle, "fertility", fertility);
target_goal += normalize(food_position - @P) * fertility;
}
if(length(target_goal) >0.01){
vector next_position = normalize(target_goal) * step_length + @P;
int next_ptnum;
next_ptnum = addpoint(geoself(), next_position);
prim = addprim(geoself(), "polyline");
addvertex(geoself(), prim, @ptnum);
addvertex(geoself(), prim, next_ptnum);
}
}
}
在循环之前的最后一步是将“耗尽”了的营养素删除掉,kill_exhausted_food,如果没有这一步,树枝永远在原地打转就好了,只有吃完了自己家的才会想到别人窝的,这个驱动力真现实。这个部分我直接使用节点实现的,灵活运用噗。
最后再附上生长素有营养贫富关系的生长效果,感觉这种算法才是最接近真是世界的。