Houdini学习笔记 | Applied Houdini - Rigid II 基础

第二节课太长了,有趣的知识点如下:
1.借助不同的voronoiFracture来创造多个cluster,通过判断点和cluster的交集来决定约束,比如:假设A点在cluster A中,同时又在cluster B中,那么他的约束就会非常强。如果只是在单一的cluster中则是弱约束;
2.借助metaball force来控制爆破位置,同时可以将force转化为point的加速度,a = f/m,当 (加速度>阈值&&约束==弱) 时,约束断裂;
3.将第一节课中的for each loop运用到wedge节点中,wedge实际是一个可以传递参数给ROP的迭代器,理论上只要需要迭代参数的都可以;
4.大量使用file cache来缓存piece和sim,以及instance的套用过程中,借助primintrinsic的相关属性复原数据中的transform,pivot等关键信息;
5.本节课教程中使用的绝对路径,规避了一些问题,比如节点间层级关系,如果使用相对路径请一定注意目录层级。

Copy and Transform 节点

H18中的copy and transform是没有办法删除原物体的,可以在copy中输出组,并使用blast节点删除

其中Pivot 不再支持 $CEX , $CEY, $CEZ 获取局部变量,使用结果如下


语法错误,节点报错

点击发现无法获取计算结果


当前计算结果为0

参照https://www.sidefx.com/forum/topic/66402/
原本的copy节点被更新取代后,
stamp copy依然可使用上述语言,但是copy and transform 和copy to point 不再支持调取局部变量,需要通过centroid()语法调用,格式如下:

centroid(surface_node, type)

The type should be one of D_X, D_Y, or D_Z 
for the corresponding components of the centroid.

centroid("../sphere1", D_X)
centroid("../sphere1", D_Y)
centroid("../sphere1", D_Z)

结果如下

重新写入centroid()表达式,获得正确计算结果

注:The centroid is the center of the bounding box of the points, not the average position of the points.
所以此处 centroid("../merge1", D_X), centroid("../merge1/", D_X) 获得效果相同,因为完全是依据bbox来计算中心,而不是其中细分的几何体。

再注:如果多次使用带有copy节点中的centroid(),复制节点时一定注意centroid()的指向问题!

此时打开geometry spread sheet 可以发现,由于几何体之间是简单的复制关系,所以在碎片name命名上存在重复,如果在这个基础上做约束,则会出现约束指向不明确的情况。


有相同名字的碎片有24个,因为copy一共产生了24个

此时可借助@ptnum来重新命名name,可以另外建立变量@name_original保留原名称作为参考。


在primitiveWangle中添加VEX更改name变量

接下来进入标准的dopnetwork流程

小技巧1:

dopnetwork中只输出rbd


*rbd指代dop节点内所有的rbd数据

小技巧2:

自建变量控制版本
可以在之前用Null建立的控制节点内添加int参数,然后将参数传给file cache内指定文件夹

在Null内加入int参数调节
将参数传给路径,中键点击Geometry File查看解算路径

小技巧3:

packededit节点可以把rbdpackedobject转为点云显示,降低显示压力


adjacentpieces调用之前借用@ptnum重新命名的@name

注:这里有个坑一定要清醒!!!

虽然是借助point来建立约束,但最终作用在primitive上,所以建立约束一定要用primitiveWrangle!!

primitiveWrangle才是最终约束的指向!

在接入glue constraint后的constraintNetwork中可以通过线框显示来检查约束是否生效


内部红线链接不同pieces,证明约束已经建立

在约束建立完成后开始解算,发现建筑底部因为重力原因出现了破损,上半部分完好


可以通过将bounce(反作用力)和friction(静摩擦)降到0来解决,但是这种方式有些不合理

此时可以通过对接触地面部分的点进行锁定来模拟地基效果,需要调用全局变量@active。

在point重新命名后,通过@P.y的高度来决定@active是True 或者False

在参数界面会自动大写第一个字母创建label。
注:此处用ch()创建局部变量参数"foundation_threshold",教程中是创建"height_threshold",局部变量可以任意命名,自由度真高啊,香!

此时可以借助delete节点来观察@active=0所对应的的面


由于选择了@active=0, 观察地基要选择delete non-selected

注:一定要注意group选择的输入格式,如下几种格式都是非法的:

i@active = 0
@active = 0
i@active=0

比较好理解,在group里空格就像是分隔符,所以一个语句不能有任何空格,i作为声明数据类型标签也不应该在这里出现。
注:可以在foundation_threshold内输入表达式 $FF/10来获得建筑物生长效果。不能用$F,因为$F返回整数值,$FF返回浮点值。

接下来要再次基础上增加约束来丰富破碎效果。

首先借助一个box来观察voronoi noise的作用效果


其实就是基于voronoiFracture的算法,点的位置P作为运算依据,将P的信息传给voronoiNoise,然后产生三维的随机Cd值,即可获得rgb voronoi色块

注:promote noise frequency可以高效使用节点

接下来用这一次的noise和之前通过adjecentpieces产生的约束建立强弱约束关系,如果几何体同时满足两个约束则为强约束,其他则为弱约束。

这里非常高能!!!建议去阅读VEX!!

primvertex官方定义

primvertex(geometry, primnum, vertex)

简单的说就是找到当前几何体的linear vertex
其中geometry如果在wrangle之类的节点内,可以用相应的输入端来代替0, 1...
primnum即为需要查找vertex的对象几何体
vertex即为需要返回哪个顶点的属性,顶点也是从0开始。

什么是vertex!!

在进行下一波之前需要了解vertices和point的区别。

Vertices和point在houdini中是不同的概念

举一个简单的例子!


一根线有两个点

vertex是比point更底层的,面向几何体对象的数据。如果一根线不够我们再加一根三个点的线!

两根线共有五个点,但是vertex分了两组

也就是说,vertex是一种类似python的json型的数据(可能有点不恰当)。第一根线也就是几何体“0”,里面有0和1两个vertex,第二根线几何体“1”里面有三个vertex。为什么说vertex更底层呢,因为vertex是不能更改的!!(虽然通过point可以间接改)

此时我们得到的也只是linear vertex,还要用vertexpoint()把vertex数据提取出来。


vertexpoint官方说明

注:vertexpoint的返回值是ptnum而不是pt的坐标,所以是整数型。 =

接下来只要判断这两个点是不是在同一个voronoi noise范围内就可以确定约束关系了!


point官方说明

point的返回值是带入的attribute name的值,也就是,可以通过point来判断点是否在这个attribute内。
那么通过point函数带入noise定义的相同属性,只要两点返回值相同,就是处在同一属性下!
Bingo!!!

让我们开始吧!
把之前建立约束名称的attribute wrangle 彻底删除!
小技巧:在wrangle输入VEX,如果内容过多,可以alt+e弹出代码输入窗口

int p0 = vertexpoint(0, primvertex(0, @primnum, 0));
int p1 = vertexpoint(0, primvertex(0, @primnum, 1));
//这里先求出两个点的vertex
int cluster0 = point(0, "cluster", p0);
int cluster1 = point(0, "cluster", p1);
//这里求出p0和p1是否在同一cluster内,作为判断依据
//一定要注意,这里的cluster是一维整数,要和之前attri VOP对应
if(cluster0 == cluster1){
    s@constraint_name = "Glue_inside";
}
else{
    s@constraint_name = "Glue_outside";
}
f@strength = 1.0;
//这里通过条件建立两种不同的约束,但是可以暂时统一约束强度,在DOP内用分别定义的Glue conrel来调节

MetaBall是一个力场而不是一个几何体

当两个引力场上的值相同时,会出现相吸的情况。

metaball

添加force节点调节相应数值,同时考虑到爆破力是瞬时的,所以需要通过添加表达式的switch来控制爆破,比如$F==3,在第三帧爆破,另一个输入连接null,注意调整次序,符合条件表达式返回True为1。
POP particle operator,早期版本中的DOP也是基于基于粒子的演算,由于metaball是基于点阵产生的场,所以同样可以用于rbdsolver中。

metaball放置在建筑中

注:popMetaBallForce scale正数为吸引,负数为扩散。另,此处说明也可以看出MetaBall和particle的关系。

此时的爆炸还不够真实,合理的情况下,在爆炸的瞬间在MetaBall范围内的约束应该是断掉的。

约束并没有断,所以效果比较勉强

分析:约束没有断也没有移位,因为在约束作为独立的primitive并没有更新位置。
需要在constraintNetwork内添加sopSolver输入,因为SOP是针对几何体的操作,而约束也是以primitive几何体的形式存在的。

在SOPsolver内加入objectivemerge节点

需要调用DOP内Geometry层级的P

教程中使用了../..rbd:Geometry
注:../..为上一层,也就是DOP层级。 rbd指向含有rbd关键字的数据。从这里也可以看出,packedObject的特征是所有的几何体都包含在一个物体之中,因为rbdPackedObject和groundPlane是平级的。所以这一步实际上是找到了rbd内每一个破碎pieces对应的点,也可以叫“拆包”。

设想一下,pieces对应的点其实就绑定了约束,所以可以把刚刚得到的@P反向输入给约束,从而达到在爆炸的同时改变约束。

findattribval官方说明

findattribval 返回的是符合指定几何体内指定类的指定值对应的点的序号point number,并不是一个三维值。
然后通过点的序号来获取对应的@P

int pt = findattribval(1, "point", "name", @name);
@P = point(1, "P", pt);

此时用之前用作爆炸的metaball作为参考,通过group来求出需要删除的约束,命名为activate。
同时,考虑到此时输出的约束为primitive,需要把此时的group promote.
注:group节点现在已经被改掉了,由于约束其实是primitive层面的,所以需要通过group promote把 point attribute 提升到 primitive attribute
此时观察geometry spread sheet,只需要把原本的group:broken 追加上刚刚的group:active 就可以了!


发现groupBroken为二值,决定了constraint是有效还是断裂

此时让我们在最早的约束基础上添加groupcopy,增加activate组属性,然后通过简单的VEX将activate组对应的primitive转变为broken = 1

在primitiveWrangle中选择activate组,然后输入

@group_broken=1;

这里为什么用@group_ 可以参考官方链接解答
https://www.sidefx.com/forum/topic/71696/
简单的说,就是为组成员赋值。对应到案例中,就是将约束中与metaball相交的primitive组变为 broken = 1 ,强行断裂!

最后,由于构成约束的sop solver需要逐帧反复调用判断,需要添加group delete,删除activate组。

此时爆破的前几帧非常拟合metaball的外形,获得的模拟还是缺乏真实性,因为pieces的爆破初速度没有明显的差别,理论上应该是质量越大初速度越小(惯性)。

在popforce中将ignore Mass取消勾选

此时已经有了force和mass,已经很容易求出加速度了,a = F/M,通过加速度值来判定约束断裂的时机,所以可以在SOP solver中更改原本的断裂方式。

一个经典错误!

通过观察Geometry Spread sheet 发现这里force一直都是(0, 0, 0)。


force数值一直为0??理论上在第三帧应该有数值

因为我在这里犯了个很经典的错误,rigidBodySolver 有preSolver 和postSolver,顾名思义就是主动加入解算和被动获得解算的方式。教程中并没有针对性的讲这里,如果连接到preSolver,那就意味着提前把数值锁定了!
https://www.sidefx.com/forum/topic/73352/?page=1#post-309990

借助length()函数可以求出矢量force的值,注意不要把length()和len()混了,len()和python里用法相同。

回到SOPsolver内,不再使用metaball创建的field。
而是借助objectiveMerge的rbd所对应的点的force来计算加速度。

依然是将rbd对应的点传primitive内,获得动态的约束
int pt = findattribval(1, "point", "name", @name);
@P = point(1, "P", pt); 
f@mass = point(1, "mass", pt);
v@force = point(1, "force", pt);

此时已经获得了当前rbd对应的点的 force 和 mass,通过attribute promote将这两个数值提升到primitive层级。然后就可以加入判断条件
1:当前约束是否达到加速度阈值?
2:当前约束是否在Glue_outside的弱约束内?(判断这个条件必须在primitive层级,所以这也是需要attribute promote的原因)

另,需要注意当前判断的对象是还没有断裂的约束,即!broken

f@accel = length(v@force)/@mass;
if(f@accel > ch("accel_high_thresh")){
    @group_high_activate = 1;
}
else if(f@accel > ch("accel_low_thresh")){
    @group_low_activate = 1;
}

此时获得了两个组, high_activate 和low_activate,
然后通过group copy将两者*activate合并入原constraint relationship。

if(@group_low_activate && s@constraint_name == "Glue_outside"){
    @group_broken = 1;
}

然后通过group delete删除 activate组。
注:SOP solver内的constraint relation 是 iterate by frame 逐帧迭代的,添加
activate 和删除*activate的存在,是为了清空属性,不影响下一帧的判断。

解算后观察细节,发现有几个碎片初速度极快,通过点选择观察属性。


再一次使用length()求出当前点的速度

在Geometry Spread Sheet中发现速度已经超过40,需要添加limiter来控制碎片初速度,在popMetaballForcepop后接入popSpeedLimit。

另,此时的metaballForce只在第三帧出现了一次,接下来需要将爆炸丰富就要让metaball的位置和大小随机化。
首先借助VDBfromPolygon来将建筑变成实体填充。


VDBfromPolygons中,选择fill interior可以将内部彻底填充。

注:此时将获得的VDB数据转化为polygon soup, 可以打开Geometry Spread Sheet来观察 polygon soup和polygon的区别,在primitive层级,polygon soup数据量更小。
再注:此时可以通过调整isovalue值来观察之前fill interior是否勾选对结果的影响。

此时相当于获得了一个“爆炸点源”的数据,然而我们只需要在某个时间的某个位置有一个爆炸源,也就是metaballForce。
可以用scatter将点分布在PolygonSoup内,当然也可以继续用isooffset的fog选项将点分散到vollum内,
只是如果已经有了isovalue来调节爆炸点的深度,再用isooffset,相当于有了两个参数来控制,反而效率不够高了(个人观点)。此时需要在scatter的point number上输入表达式,考虑到只想要一个爆炸点在建筑物内“游走”,可以通过条件语句来控制,因为条件语句的返回值是 0 和 1。

@F%3==0 && $F<20

表达式内竟然也可以用取余符号!所以需要提取小于20且为3的倍数的帧。然后通过$F来调节seed值来随机化生成位置。
考虑到既然是随机位置,应该也有随机大小产生,在pointWrangle中添加随机化的pscale

f@pscale = fit01(rand(@Frame), 0.3, 0.85);

此处因为rand()的返回值在(0,1)之间,刚好可以运用fit01(),
fit01(num, newmin, newmax); 会将(0,1)的返回值重新映射到新定义的阈值区间。
然后通过copy to point, 将metaballForce和点绑定,就获得了随机化的metaballForce!

将metaballForce作为popMetaballForce接入

借助File cache来缓存piece

可以用delete来控制选择,用之前connectivity创造的属性@class配合$F来控制序号。
此处有坑:所有以string形式存在的表达式都要加`` 而不是‘’。
https://www.sidefx.com/forum/topic/73399/

filecache因为是逐帧输出,也充当了迭代器

借助assemble里的nprims()来确定迭代次数,记得-1因为nprims是从1开始的,而piece或者class属性是从0开始

此时可以继续拉取controlPanel上的数值作为存储路径参考,在命名方式上还是调用原piece的name属性。
注:在这里其实我的control手误打成了contrl,导致后面很多顺手打而不是copy的路径出现了错误,所以在输入路径时尽量拉取避免输入错误,不要因为打字一时爽就...

此时我们已经获得了piece的数据,相当于减轻了voronoiFracture这部分的计算量,接下来只要把通过sim输出的particle数据套用现有的piece数据就好了。

借助File cache来缓存sim

通过提取sim内的点数据,然后套用给缓存出来的piece。理论上现在除了点的name* 以及v速度w角速度数据,其他都可以通过attribute delete删除。
接下来通过instance的方式将缓存出来的piece COPY到点上,这里通过instance节点。instance本质上是把object merge和 copy to point结合到了一起的节点。
在此之前,需要通过point wrangle将name和piece绑定。
这里就要用到之前name_original。


实际上是为点添加对应piece的path数据
instance节点中的描述 attribute为对应文件的path

注:Use Object Transform并不是把点的transform信息应用到piece上,而是把piece的transform应用到点上,所以此时的piece相当于只是简单的copy到了对应的点上,并没有按照初始化的坐标来排列。
初始化位置,time shift指向第一帧。
然后通过point wrangle将原sim cache中的位置信息写入instance。


读取原数据中的primintrinsic并更新到instance中

transform的数据是matrix3,相当于是存储点的旋转信息, packed transform是 matrix4。
有了旋转角度,还需要知道位置和旋转中心。primintrisic提取并用setprimintrisic来为instance添加信息。
至此,基本框架已经基本达成了。

Wedge 迭代输出利器

此时的sim输出只有一个版本,通过wedge可以将参数迭代

channel对应control中需要控制的wedge版本

wedge的driver来自sim cache内部的ROP,range的最高值设定为迭代次数-1。

在sim cache中同样需要根据不同的wedge进行迭代输出。


添加不同wedge数值的路径

此时只是设定了不同wedge的输出路径,接下来需要把wedge的相对参数复制到需要调节的参数上,比如constraint 的strength 可以用 pow(10, "wedge相对参数"),代表每个版本的strength是指数型增加,或者细化到 5000 + 1000*"wedge相对参数",这样一次输出可以得到多个约束强度的效果。
另一方面,也可以把wedge传给force生成的位置,也就是scatter的random seed,让每次爆炸点都有不同。

查看wedge结果

用file merge和transform作为迭代的内容,

此时file merge的地址需要把wedge参数替换为iteration。
依然用

detail("../repeat_begin1_metadata1/", "iteration", 0)

注:在目录中作为字符串出现前后添加``!
transform根据迭代detail来自定位置就好了。
根据wedge结果选择理想的参数,至此解算告一段落。

接下来要在此基础上增加piece的丰富程度和细节。

增加更多的piece

在原来的voronoiFracture基础上增加for each loop

scatter内点数用迭代语句控制随机,然后用fit01()控制range。
switch依然是用

rand(detail("../foreach_begin1_metadata1/", "iteration",0))>0.6

60%的几率会被打散成更小的碎块。
注:这里添加了divide节点,会强行把多变面变成三角面,避免normal产生问题。

再注:此时因为第二次使用voronoiFracture,默认产生的inside和outside组会覆盖掉之前的组,所以建议添加后缀,如inside_sub outside_sub。

增加piece inside的细节

基本思路是为inside组增加噪波,但是由于piece棱上的点也算inside组,所以需要借助iso offset来选择一定距离的组。


SDF Volume产生距离场

SDF全称Signed Distance Field,如果物体在SDF Volume内部则返回负数,反之为正数。借助attribute from volume 将距离信息保存在depth参数下。

此时只需要锁定depth在一定范围内的点不产生noise就可以了。


引入depth参数,并添加fit,promote最大值作为外部调节参数

最大值映射到1,1乘以noise还是noise。而数值越小,形变越小。这样做就可以把一定depth距离内的点的数值锁定不受noise的影响了。

再次借助divide来增加细分

同时这时候也需要退回到建模初期,把四条边选出来,建立group,命名boarder,方便后面选用,因为噪波是不能出现在boarder上的。

为boarder添加浮点值1,然后借助visualizer查看范围

此时通过调节attribute transfer 内的distance threshold和blend值,达到渐变目的,也就是从从noise到 boarder的ramp。


复制一个VOP,用1-boarder的距离作为noise的乘数

因为boarder初始值为1, complement刚好是 1- arg,刚好作为控制boarder的方法,太妙了!

至此,破碎的细节终于制作完成了!
之前的版本可以保留作为instance_lo,输出作为 instance_hi,


在套用instance之后添加switch节点,切换高低模

最终输出前可以再跑一遍sim和instance,统一版本号。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341