Unity-针对DynamicBone插件的迭代来实现布料模拟

这篇博客介绍了我在近期针对于Unity的DynamicBone插件进行迭代,从而实现近布料模拟的方法。

由于部分涉及到工作内容,因此询问了部门老大后决定本博客将只介绍迭代思路,不涉及到具体实现,也不会放出对应图片。


背景

来上海也有近两个月了,近来也差不多安定了下来,也是时候恢复写博客的习惯了。

最近的任务就是针对于Unity下的DynamicBone的迭代,让其能够更加适配于我们自己的项目(主要是布料的模拟)。

关于原生的DynamicBone

原生DynamicBone介绍

DynamicBone是一个Unity的插件,可以用于针对骨骼进行物理模拟。由于开放源码并且工具链集成方便,因此颇受项目青睐 —— 我也是通过直接阅读该插件的源码来逐渐接触Unity开发的。

DynamicBone开放了DampingElasticityStiffnessInert等物理属性接口,并且开发者可以通过配置DynamicBoneCollider来配置相应的碰撞体,来进行碰撞结算。

DynamicBone允许开发者指定对应的根骨骼,从而允许该骨骼的子骨骼进行物理结算,而根骨骼将不进行物理结算。换句话说,这样的物理模型更加适用于例如尾巴、马尾、Ragdoll等带有明显层级结构的物理模拟。

直到我入职为止,DynamicBone已经更新到了1.1.7版本。需要吐槽的是……在阅读源码之后,我个人认为对应的物理模型和结算逻辑经过作者的多次改动后,似乎已经比较混乱了。例如对应的Force和Gravity结算、外力结算的Integration都不太好阅读。

原生DynamicBone的工作原理

DynamicBone的物理逻辑与Tressfx、以及我的HairStrandPlugin(没错,这是一条广告)差不多,都是将需要进行物理模拟的对象抽象为Particles,然后针对于Particles进行物理逻辑运算。在进行完物理逻辑运算后,再根据Particles的位置来进行对象transform的设置。

DynamicBone的工作原理是使用松弛法,每次迭代分为Verlet IntegrationShape KeepingLength KeepingCollision Solve四个阶段。


Verlet Integration

不多说,可以参考我之前构建HairStrandPlugin的开发日志,传送门


Shape Keeping

原作者的Shape Keeping采用的是Local Shape Constraint。也就是说针对于每个粒子,每次迭代都会试图将其掰到其父粒子的原始相对位置上去。


Length Keeping

Length Keeping的方法是针对于每个粒子,都会将其限制在以其父粒子为球心,两个粒子初始距离为半径的球内。这个方法在01年的Game Developer Conference被 Jakobsen T.提到。


Collision Solve

Collision Solve的方法是进行球与胶囊体的碰撞检测,如果相交,则将粒子球推到对应的表面去,简单的几何计算。


每次的迭代都将经历上面的四个步骤,原作者将最大的迭代次数限制在4次以内。

针对DynamicBone的新需求

在现在的项目中,我们需要使用DynamicBone来实现类似衣料的模拟。布料的物理模型与原生DynamicBone的物理模型差异较大,经过分析后,物理模型的差异可以归为如下几点:

  • 弯曲弹性(Bending Resistance),由于在原有的物理模型中,整个骨架的弯曲是没有专门的Constraint来进行结算的,因此整个DynamicBone的效果看起来会很像一根软绳。并且如果某根骨骼进行了碰撞的结算,那么整体的形状会看起来比较怪异,不够顺滑。
  • 弹簧-质心系统(Springarm-Mass system),由于项目的逻辑涉及到外力,因此物理模拟的构建必定少不了质心系统,提到质心系统就肯定是弹簧系统。例如比较靠近根骨骼的粒子,应该将其质量设置的相对较大,而比较靠近尾端的骨骼,质量就会较轻。
  • 风力系统,没啥好说的,暖暖是个换装游戏。
  • 定制化的弹性约束系统(Customizeable Springarm Constraint system),这一点是重中之重。由于我们尝试使用DynamicBone来模拟衣料,而原生的DynamicBone是有层级架构的,因此不同骨骼树下面的骨骼之间是没有约束的。这样也就导致了衣料碰撞结算会是一个极不稳定的结算 —— 当碰撞体穿过几串骨骼之间时,这些骨骼将从碰撞体边缘滑落,这是完全无法接受的。

针对新需求的迭代思路

弹簧-质心系统(Springarm-Mass system)

之所以把这个东西放到第一个讲是因为这是其他几个物理模型的基石。其实实现起来挺简单的,针对与每一个粒子加入一个质量(mass)属性,然后针对弹簧结算,是依据其质量的比例,来将其推向稳定位置。这样一来靠近根节点的粒子将更加重一些,而靠近尾端的就会更加飘逸一些。

弯曲弹性(Bending Resistance)

这个简单,在粒子与其孙子粒子之间加入一个本地形状约束弹簧即可。

风力系统

也简单,只是适配一个按照时间变化的向量曲线即可。

定制化的弹性约束系统

虽然前面说的比较重要,但那是功能层面的。实际的实现上还是比较简单,只需要在对应的骨骼上加上长度约束弹簧。这里比较麻烦的地方是在将各种的配置保存至.prefab文件上的操作,但那一块不属于这篇博客覆盖的内容,因此略过。

性能分析及优化

性能分析

我使用了Unity自带的profiler进行了DynamicBone对应的性能分析后,发现性能hot spot如下:

  • 各种ConstraintSolve,例如弹簧的结算,Shape&Length Keeping结算等…
  • 碰撞运算
  • World2Local,Local2World之类的坐标系转换的运算。

在这其中,ConstraintSolve占大头,碰撞其次,坐标系转换运算再次。

值得注意的是,由于Verlet Integration的特点,导致计算的时间步长必须是固定的。这也就导致了在某些情况如果帧率忽然降低的话,会导致下次的迭代次数增加,从而导致下次的迭代计算时间变得更大,从而恶化下下次的迭代计算时间。

因此,如果项目如果在锁帧方面有特殊需求的话,需要额外注意这一块的问题。

优化方案

硬优化

首先是硬优化,一些细枝末节的优化方案(例如距离判断不要进行开方而是直接判断其平方的大小关系之类的方案)就不提了。有一块我做了额外的处理,也就是胶囊体的优化。

胶囊体的碰撞计算需要首先计算出胶囊体的Transform信息,也就是说Position,Rotation,Scale。其中实际上Scale信息可以约定好不进行采用。

这样一来,我们需要获得Position信息与Rotation信息,但是这么说来在结算过程中,需要额外考虑胶囊体的朝向,这样也就意味着多了不少的计算操作。

因此我创建了球形的碰撞体,并且与团队成员约定在保证效果的前提下,尽量多使用球形碰撞体来进行检测碰撞,这样可以节省一些性能。

还有,可以定义对应粒子的懒惰程度。每个粒子在运算过程中,可以大略估计出其懒惰程度 —— 如果该粒子经常与碰撞体产生碰撞,并且involve进了很多的弹簧系统中,那么我们认为这个粒子是很勤快的;否则,我们认为这个粒子比较懒惰。这样,可以减少一些粒子的迭代次数。

最后就是多线程优化了。这一块在我的HairStrandPlugin中也有提到,这里不再赘述。

软优化

然后是软优化。首先,我将最大迭代次数变为一个可调整量。

为什么要这么做呢?因为在迭代过程中,往往第一次迭代的效果是最大的,而越往后的迭代只是将其往最精确的位置推一点点而已。而在Verlet Integration的逻辑下,哪怕是推一丁点位置,也需要一次完全的迭代。

这也就意味着对于一些并不需要那么精确的地方,我们可以将最大迭代次数设为一个较小的值,甚至为1。

然后,我们鼓励美术将动态骨骼在合理的条件下尽量分配为多个DynamicBone。这一块是为了碰撞的计算优化。以裙子为例,如果是一整个DynamicBone,那么即便是左腿左侧的那几根骨骼,也需要和右腿进行碰撞检测(尽管他们永远不可能碰撞)。

但是如果分成两块DynamicBone,那么就可以避免掉一些完全不需要的碰撞检测,从而提升性能。

优化结果

最终的优化结果,可以在红米3s上,支撑300+的动态骨骼的实时结算(这还是多线程优化未实装的测试结果,如果多线程优化实装上去,估计性能会更好)。

总结

读者可能会觉得我一直在吐槽DynamicBone的不足之处,但是其实不是的,DynamicBone的工具链适配思想这一块是我认为非常漂亮的地方。

我所做的工作也是将其迭代成为更加适合我们项目的工具,毕竟原生的DynamicBone并不是为了我们这个项目而开发的,它要考虑的是更加普遍的适用性。

但是反过来想,将普适的工具迭代为适合项目的特殊工具,这似乎也很了不得呢(此处应有掌声)!

 

简易设置:

模型导入装配什么的就不说了。

1.要做动作的模型,Rig—-从这个模型创建Avatar   Animations—取消导入动画。

2.有动画的模型,Rig—-从其他模型创建Avatar,选中新模型的Avatar。

3.动画文件选择 Rig—-从这个模型创建Avatar。全部选中humanoid。

动力学骨骼插件DynamicBone:

《Unity-针对DynamicBone插件的迭代来实现布料模拟》

1.把DynamicBone脚本放到角色上。

2.Root放入要进行动力学的根骨骼。

3.Colliders放入要进行碰撞的骨骼数组,该骨骼上要放上DynamicBoneCollider组建。

其他的参数都很简单,一看就知道怎么用了,这个插件东西不多但是效果不错,很好用。

UpdateRate:更新数率

damping:阻尼系数

Elasticty:弹力

stiffness:僵硬度

inert:迟钝系数

DynamicBoneCollider:

可调节碰撞体的大小,方向。

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注