首发于Game Fantasy
动态骨骼Dynamic Bone算法详解

动态骨骼Dynamic Bone算法详解

题外话:今天在看关于Jobs发现unity2018.2很有意思,对于animation进行了很多的尝试,相关具体可见,后续看看能不能抄到2017.4上来

Animation C# Jobs – Unity Blogblogs.unity3d.com图标Unity-Technologies/animation-jobs-samplesgithub.com


下面开始正题:

DynamicBone是一个简单的基于模拟弹簧振子的算法实现树状柔体的物理模拟插件。虽然基于模拟弹簧振子运动的算法实现,但是DynamicBone各节点之间的距离实际上不会发生变化。比起弹簧,父子节点之间的相对运动更接近串联的单摆

整体流程简图

DynamicBone模拟出的柔体特性体现在物体运动之后发生的弹性形变上。所以它的模拟自然发生在物体运动之后,再根据物体运动的结果去模拟各个节点弹性运动的过程。

下面得到的DynamicBone的整体流程非常简单,当物体原本移动完成后,首先根据运动的当前结果模拟弹性运动,再根据模拟结果修正各节点的旋转并且将模拟结果同步到实际节点上。

这其中,我们称图中(2)的物体各节点的原本移动结果为理想位置,因为有弹性的物体虽然会发生形变,但是其最终会尝试还原到原本的形状。而模拟运动的结果则称之为实际位置。因为这是计算得到的结果,并且最终也会同步到附属的节点上。

整体流程图

为了便于区分,这里我们称DynamicBone的节点为节点,而实际的物体节点为附属节点

篮框部分考虑了力的影响使用了韦尔莱积分法,红框部分整体就是弹簧运动的模拟计算部分

预处理:附加全局位移

•在弹簧运动模拟计算开始前,DynamicBone首先会将现在所在的物体的位移乘上惯性(Inert)参数之后附加给所有节点。这段位移是不会参与到后续的运动模拟计算过程的,会直接改变当前帧和前一帧的实际位置,所以这段位移在后续计算中不会被算入速度。即

弹簧运动模拟部分1:惯性运动&受力运动

弹簧运动模拟计算的第一部分是惯性运动模拟,这部分非常好理解,就是让节点保持速度继续运动,而速度的值就是一次模拟内节点的实际位置的变化差值减去阻尼(damping)值,即

弹簧运动模拟计算的第二部分是受力运动模拟,DynamicBone中考虑了两种力对节点运动的印象,分别是重力(Gravity)常驻力(Force),这两种力对运动过程的影响方式不一样。这里常驻力很好理解,是无条件发挥作用的力,会直接附加到计算结果。而重力的机制比较特殊,重力本身不发挥作用,只有运动发生后的重力分量会影响运动过程。即

弹簧运动模拟部分2: 重力计算补充讲解

重力计算

在初始状态下,重力不起任何作用,但是DynamicBone会记录重力在根节点局部坐标系内的向量。在每次计算时,DynamicBone会计算变化后的局部重力向量在初始重力向量方向的投影,最后为节点附加初始重力向量减去这个投影向量的重力。由于记录的是根节点局部坐标系内的向量,所以只有根节点发生了旋转才会导致重力发挥作用,而其他的节点的旋转不会。还有只有当局部重力投影与全局重力方向一致时,全局重力才会减去投影。如果方向相反(意味着根节点旋转了超过180度),全局重力只会全部发挥作用,不会去加上投影长度。

弹簧运动模拟部分3:弹性运动&刚性运动

弹簧运动模拟计算的第三部分是弹性运动模拟,属于节点自主运动,即不受力和速度的影响,并且运动结果会被视作速度(所以本质上可以视作一种内在力)。弹性运动的过程非常简单,就是求出节点当前实际位置与理想位置的差值,并且乘上弹性(Elasticity)参数附加到实际位置。即

弹簧运动模拟计算的第四部分是刚性运动模拟,也属于节点自主运动。要解释弹性运动首先要引入理想距离,即父子节点理想位置间的距离,也就是父子节点原本距离。刚性模拟会先根据刚性(Stiffness)参数和理想距离求出节点偏离理想位置的最大距离,并且将节点限制在距离内。即

弹簧运动模拟部分3:弹性运动&刚性运动

弹簧运动模拟计算的第三部分是弹性运动模拟,属于节点自主运动,即不受力和速度的影响,并且运动结果会被视作速度(所以本质上可以视作一种内在力)。弹性运动的过程非常简单,就是求出节点当前实际位置与理想位置的差值,并且乘上弹性(Elasticity)参数附加到实际位置。即

弹簧运动模拟计算的第四部分是刚性运动模拟,也属于节点自主运动。要解释弹性运动首先要引入理想距离,即父子节点理想位置间的距离,也就是父子节点原本距离。刚性模拟会先根据刚性(Stiffness)参数和理想距离求出节点偏离理想位置的最大距离,并且将节点限制在距离内。即

碰撞检测与恢复4

DynamicBone的碰撞检测很简单,就是将每个节点与每个碰撞体逐个进行碰撞检测并且根据检测结果进行恢复。DynamicBone提供了两种形状的碰撞体,球型和胶囊体型。球型的碰撞检测非常简单,首先求出节点与球心的距离,然后求出节点半径与碰撞体半径之和。如果距离大于半径之和(在Inside模式下是小于),就移动节点保证距离等于半径之和。

胶囊体的碰撞检测要稍微复杂一点,首先求出胶囊体两个球心之间的向量A,然后求出节点中心与其中一个球心的向量B,然后求A在B上的投影,根据投影结果在进行下一步判断,即

碰撞检测与恢复

然后,当投影方向与向量A相反,则节点相当于进行与以球心A为球心的球体做碰撞检测与恢复;如果方向投影方向与向量A相同但是比向量A长,则节点相当于进行与以球心B为球心的球体做碰撞检测与恢复。

如果如果方向投影方向与向量A相同并且比向量A短,则取向量B在与向量A垂直方向上的投影向量长度,如果该长度大于半径之和,就移动节点保证距离等于半径之和

后处理阶段5:维持节点间距离

在模拟计算最后,DynamicBone有一个简单的后处理防止父子节点之间发生拉伸或者压缩,维持节点间理想距离。步骤很简单,首先求出当前父节点实际距离的差向量,然后将其恢复至理想距离向量。即

后处理阶段6:修正节点旋转并且同步变换

在最后将计算得到的节点变换同步到附属节点前,DynamicBone还会根据各级节点间的相对变换做一次旋转修正,每对父子节点只要父节点仅有一个子节点就都会让父节点旋转至与子节点的相对旋转的初始值相同的状态。

缺陷与优化

缺陷:

DynamicBone的缺陷主要是不允许节点间距离发生变化,这导致DynamicBone不允许实际的拉伸或者压缩发生,DynamicBone的节点之间实际上缺乏弹簧的性质。因此DynamicBone不能模拟容易发生拉伸形变的物体。

另外一方面DynamicBone所有计算都不考虑时间因素,所以同样弹簧参数不同更新频率参数下表现会非常不同,加大了调参难度。

优化:

最常用的LOD降频方式或者远处动画

代码C++化,测试了下相较未IL2CPP下面能快2倍

考虑同场景多个DynamicBone对象的计算情况,合并计算流程统一管理

对于差不多位置的DynamicBone进行近似计算,计算一跟复用多跟,比如飘带,裙摆等

进行jobSystem化,放在BatchedIKPass之后

Unity中Transform只存储了Go的local相关的信息,计算Gloabl的相关数据时候会进行各种转换,这个也是导致耗时的主要原因

编辑于 2018-11-10

文章被以下专栏收录