公司特效组最近一半的人一直都在做着修穿插这样的一个重复的事情,听说来公司之前大家也都一直这样做着,这种完全不能把艺术家从重复劳动中解放出来的状态确实有点让人神伤。最近本人也在做着这方面工作,想借着这个机会,总结一下自己这方面的经验,也重点探讨一个能够适用常规多数角色穿插问题的解决方法。希望能做到抛砖引玉的效果。
首先聊一聊角色动画中常见的两种穿插情况。
第一种比较常见的是角色与其他物体的穿插,比如手拿杯子脚踩地。这种情况的解决方法比较灵活,因为是两个物体,所以常常可以使用volume的那套方法找到穿插的位置在用gradient或者ray的方法解决穿插的问题。另外这种情况往往我们可以很清楚的定义穿插面和被穿插面,两个虽然理论上是相互影响的,但是处理起来我们完全可以忽略掉被穿插面(被角色碰到的区域)的变形问题,因为大多情况是根本不用变形,所以变形的变数是很好控制的。
还有另一种情况的角色穿插就是由于前端rigging流程没有做到百分中百的角色防穿插或者类似自己的手拂动自身的肉这种情况,首先我们是在一个solid surface上找到不同的面之间的穿插,如果引入volume的那套方法就意味着我们要依靠人为的判断,手动的在穿插之间的地方将角色砍开,在分别做处理。这种方法无疑是离不开重复劳动,而且如果穿插位置有好几处的话,大概拿到镜头的人就只会想怎么cheat了。
估计如果大家真有在houdini里面修穿插的经历后基本上都会了解怎么使用volume和ray来处理了。我这里只想重点聊一聊第二种情况下,如何程序化的搞定穿插问题。
解放艺术家的双手,让他们去做更有创造力的事情。 -- 机器猫
先看一看效果:
变形前
变形后
先讲一讲思路,再一个一个细聊:
1,使用intersect方法找到交叉位置
2,将穿插位置的法相方向统一
3,用新的法线在找出来的区域重新做一次intersect得到较准确的挤压偏移量displacement
4,找出交叉区域露在外面的边缘
5,柔化外边缘后根据法线和边缘权重向外挤出一点budge
6,统一给穿插以及边缘部位做平滑smooth
在整个过程中因为测试的几何体细分程度有比较大的不同,发现直接使用原mesh上的点做上面的采样与计算,结果是非常不稳定的,而且最后的动画穿插部位会有很严重的抖动问题。因为随着动画的运动,穿插部位的点进入穿插和离开穿插区域都会对之后的变形产生比较明显的影响,说白了就是采样点不够密。所以在这个讨论的改进版本中,使用了scatter给物体撒点来采样的方法。散的点密度越高,面片抖动的问题就会越小。这个方法要求的就是scatter撒的点在动画过程中相对位置是固定在mesh上的,这个使用uv的方法是不难实现的。
1,使用intersect方法找到交叉位置
用撒完的点云在三角面片化的mesh上进行一次intersect计算。判断穿插与未穿插其实也不难,如果我们沿着向外的法线去求解的话,如果碰到自己mesh的次数是奇数次的话那就说明这个点是在几何体的内部,如果是偶数次的话则说明是没有穿插的点。道理自己想。
值得一提的是如果使用intersect这个功能的话,碰撞的到的prim的法线方向与入射的法线方向接近90度的话是非常容易对下一次迭代的计算长生干扰而出现错误的。这种情况类似于入射的法线切过曲面表面。在这里我使用的是向量点乘的方法避开这种比较敏感的情况。另外针对于穿插的区域以及边缘区域,我在整个过程中使用的红色来代表的,这样可视化的程度也高一点。
这个可能算是这个方法里面最重要的一步了,就是用它找到了自身穿插的部位。
1 vector startPos = @P + normalize(@N) * 0.00001; 2 3 vector rayDir = normalize(@N) * 100; 4 5 vector collidPos; 6 float myu, myv; 7 int flag = 1; 8 int count = 0; 9 10 while(flag){ 11 int primNum = 0; 12 13 primNum = intersect(1, startPos, rayDir, collidPos, myu, myv); 14 15 if(primNum != -1){ 16 //[email protected] = primNum; 17 vector primNor = prim(1, "N", primNum); 18 //[email protected] = primNor; 19 20 if(dot(normalize(primNor),normalize(@N)) > 0.05){ 21 startPos = collidPos + 0.00001 * normalize(@N); 22 count++; 23 }else{ 24 break; 25 } 26 }else{ 27 break; 28 } 29 } 30 31 if(count % 2 != 0){ 32 33 @Cd = set(1,0,0); 34 }else{ 35 @Cd = set(0,0,0); 36 }
2,将穿插位置的法相方向统一
这个步奏就稍稍需要一点技巧了,统一或者模糊任何一个属性的话是需要与相邻的点进行平均。而穿插的问题就是不同法相的点已经搅合在一起了,所以使用穿插状态的点云来统一法相,效果会适得其反。这里我们就需要引入T pos模型了,做动画的肯定会有T pos这个状态的模型给你调用的。
解决法相统一的问题是为了之后沿着新的法相做偏移做准备的。因为穿插的部位往往是具有弧度的曲面,那么在挤出穿插部分时,如果使用原来的法相,有可能会出现挤出后点变得混乱。如图:
虽然上面示意的可能有点极端,但是这就是为什么我们不推荐使用原来的法相来进行挤出了。
这里说到的统一其实也是没有达到绝对意义上的一致,只是让法线与相邻点的法线更加有相关性。
3,用新的法线在找出来的区域重新做一次intersect得到较准确的挤压偏移量displacement
有了第一步做准备,这一步就没什么门槛了,再重复一次intersect,只不过这次只是在之前被检测的区域内反向进行计算。
4:找出交叉区域露在外面的边缘
这里我们使用的方法是根据原来检测出来的区域范围使用pcfilter的方法把选区扩大,当然filter会改变已有选取的值而且边缘也是渐变模糊的,我们直接使用大于0的判断重新把值变为1就好了。在用这个扩大了的选区减去之前的选区就得到了围绕穿插位置的一个圈圈选区了。这个过程用到了点云计算所以依旧是挪到T pos下面做的处理。
5,柔化外边缘后根据法线和边缘权重向外挤出一点budge
这一步没什么好讲的了,就是把上一步的选区柔化一下,然后当做权重值来挤出穿插的边缘区域,让碰撞的地方更有肉感一点。
6,统一给穿插以及边缘部位做平滑smooth
这里可以直接使用smooth节点,或者再之前使用neighbours找出邻居点并平均邻居点位置来确定自己的位置,这样能够更平滑一点。
终结一下,这个方法能完善的地方其实还有很多,比如如何然穿插偏移量更精确,以及让scatter点分布更均匀。尤其是scatter这个环节,H14上的scatter能够均匀分布了,但之前了解到的这种通过迭代来均匀分布的方法也不能100%的保证点云能够完全跟着动画mesh走。这些方面再以后有机会了慢慢完善吧。
最后是这个方法的一个文件,大家可以参考一下,也很希望得到意见与反馈。