本文使用向量余弦判断相似的方法,来选取两个动画中最接近的一帧进行融合,以达到相对流畅的融合效果。另一方面对于一段循环动作,根据当前运动姿态来自动匹配最佳的动画片段。
姿态相似
两个动作是否相似,可以简化成两个动作里所有骨骼的位置旋转和缩放信息组成的矩阵是否相似。
在开始的尝试里,本来打算使用SVD降维骨骼矩阵来得到特征向量,但是困难出现在如何利用特征向量上。
后来开始尝试一些朴素的向量相似方法,比如欧式距离和余弦角度。使用向量相似的一个好处是可以分别设置权重来凸显相对重要的维度信息。
假设一个动作的一帧是一个矩阵,定义每一行是各个骨骼,列是Transform的信息。因此一个动作就是若干个这样的矩阵。
\[\begin{Vmatrix} \ & pos & rot & scale \\ root & \ddots \\ bip1 \ & \ & \ddots \\ bip2 & \ & \ & \ddots \end{Vmatrix}\]两个姿态$Mi$和$Mj$(一个动作里的两帧或者两个动作里的各一帧)的欧式距离就是 \(Distance(Mi,Mj) = \frac {\sum \sqrt{(Mi_n - Mj_n)^2}} n\)
下标n表示矩阵的某一列。 同理,两个姿态的余弦值是
\[Cos(Mi,Mj) = \sum \frac {Mi_n \cdot Mj_n} {\left\| Mi_n \right\| \times \left\| Mj_n \right\|} \div n\]对于欧式距离来说,值越小,姿态越相似;对于余弦来说,值越大越相似。 如果希望加权,那么在每个列上乘相应系数就行,但是下文没有精细调整各个维度的权重,使用的平均值。
动作融合的相似矩阵
有了上文的相似判定后,我们就可以定义动作A如何更好的切换到动作B了。通常来说,我们会定义每个动作的起始帧和结束帧都和Idle动画接近,但是Idle本身也是有呼吸运动的。如果这个呼吸幅度不算小,那么这个衔接和融合就不顺畅了。如果角色中奔跑过程中再播放全身动作,那么这个衔接就变的更困难了。
如果能枚举出每个动作每一帧到其他循环动作最相似的一帧是多少,那么在这个动作被打断时就把目标循环动作切换到相似帧就可以让动画融合更平顺。
先定义动作融合的相似矩阵,以余弦相似为例:
\[clip: attack \begin{Vmatrix} \ & stand & walk & run\\ t0 & (t0:0.99) & (t26:0.93) & (t13:0.59)\\ t1 & (t49:0.79) & (t29:0.86) & (t9:0.60)\\ t2 & (t50:0.74) & (t30:0.83) & (t9:0.59)\\ \dots & \dots & \dots & \dots \\ t37 & (t7:0.97) & (t29:0.96) & (t12:0.61)\\ t38 & (t4:0.99) & (t27:0.95) & (t12:0.60)\\ \end{Vmatrix}\]第一行是要切换的目标动作集合,第一列是当前动作的每一帧,矩阵中的各个元素是当前动作帧要切换到目标动作的最佳帧和相似度。
到这里,动作切换时只要知道当前动作运行了多久,目标动作是哪个,那么就查表可知结果。下面分别是站立和奔跑中释放技能的案例。
筛选最佳衔接动作
还是利用上文提到的相似矩阵,如果我们变化一下参数,枚举出每个循环动作的每一帧到其他动作的第一帧的相似度,那么就可以横向判断比较接下来的哪个动作更合适播放。
\[clip:run \begin{Vmatrix} \ & stand\ jump & left\ jump & right\ jump \\ t0 & (t0:0.89) & (t0:0.92) & (t0:0.94) \\ t1 & (t0:0.89) & (t0:0.91) & (t0:0.93) \\ t2 & (t0:0.88) & (t0:0.90) & (t0:0.92) \\ \dots & \dots & \dots & \dots \\ t16 & (t0:0.90) & (t0:0.93) & (t0:0.94) \\ t17 & (t0:0.90) & (t0:0.93) & (t0:0.94) \\ \end{Vmatrix}\]上面举例的情况是角色在跑动时跳跃,是适合左脚起跳还是右脚起跳。判断方法就是把当前帧的这一行取出来,找到相似度最大的那一列,列名就是最符合的衔接动作。
我们还可以跟上面的功能组合起来,在起跳阶段使用衔接相似矩阵找最合适动作,落地阶段在融合相似矩阵里找到最佳融合帧进行融合。
但是这样还不够完美,因为这个相似筛选都是基于静态的位置相似,没用运动趋势信息,所以得到的衔接或融合结果可能是违反上一次运动趋势的。
把运动趋势考虑进来
假设使用余弦相似,我们在制作衔接相似矩阵时就连续计算多个相邻帧的平均值作为相似:
\[Cos(Mi_{[m,m+n)},Mj_{[0,n)}) = \frac { \sum_{k=0}^n{Cos(Mi_{m+k}, Mj_k)}} {n}\]这样计算的相似性时,一帧暂时的极大相似就被连续的运动趋势抹平了。
接下来在筛选阶段,我们连续取当前帧和未来相邻的多个帧数据,也就是相似矩阵里的多行。再比较出最大相似的行和列。比如:
- 当前动作执行到了run动作的第t10帧,执行起跳命令
- 在衔接相似矩阵里连续筛选了5行数据,比如t10-t14
- 找到相似值最大的元素
- 假设位于t13行,left jump列。
- 得到的最佳帧和当前帧还差3帧,那么就延迟3帧播放left jump动作。
看一下效果:
性能分析和展望
其实方案到这里和丐版的motion match还相差甚远,原因是前面的讨论都是基于静态分析的几个动作,而真正游戏运行时会有很多半身融合或者IK影响的姿态问题。
空间时间复杂度
假设放弃实时计算相似性,先分析一下静态的相似匹配的空间和时间复杂度。
融合相似矩阵的空间开销是n个二维表,n表示非loop动作数量,每个二维表里有m列,m表示loop动作的数量。运行时的查找开销是常数O1的。
衔接相似矩阵的空间开销是m个表,n个列,运行时的查找开销是线性On的。
而最重要的相似矩阵的计算是离线的,可以开发时统一生成。
展望
面对质量要求越来越高,动作数量越来越多的需求下,这套朴素简单的相似性匹配逻辑能提供相对自动的融合和衔接方案,减少动作融合中的不和谐表现。
目前的小样适用于其他全身动作到loop全身动作的融合和loop动作到其他动作的衔接匹配。于此同时还可以指定当前动作的后摇区间,让目标loop动作提前结束当前动作以最佳匹配融合效果。
未来也行可以通过修改筛选逻辑,针对性的分多层加权判断相似性来做到半身融合的匹配。
其实这套方法可以不在游戏运行的融合中使用,也可以在验收动作时,单独求出两个动作每一帧的相似性,以此来评判动作本身衔接融合有没有问题。