第二节课太长了,有趣的知识点如下:
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内指定文件夹
小技巧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中。
注:popMetaBallForce scale正数为吸引,负数为扩散。另,此处说明也可以看出MetaBall和particle的关系。
此时的爆炸还不够真实,合理的情况下,在爆炸的瞬间在MetaBall范围内的约束应该是断掉的。
约束并没有断,所以效果比较勉强分析:约束没有断也没有移位,因为在约束作为独立的primitive并没有更新位置。
需要在constraintNetwork内添加sopSolver输入,因为SOP是针对几何体的操作,而约束也是以primitive几何体的形式存在的。
需要调用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来计算加速度。
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/
借助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 loopscatter内点数用迭代语句控制随机,然后用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,统一版本号。
网友评论