月度归档:2014年10月

蒙皮骨骼动画技术原理

1.前言

骨骼动画技术在计算机图形创作以及游戏开发中都占据了非常重要的位置。在骨骼动画技术出现之前,顶点动画和刚体分层动画是主要的3d动画实现手段。然而在肢体运动方面,他们都有或多或少的不足。骨骼动画则提供了较好的解决方案,因此自从《超级马里奥64》等第一批使用了骨骼动画技术的游戏诞生后,它就一直被广泛使用。

2.预备知识

在骨骼动画中,每个需要运动的顶点都被关联至某一个或多个骨骼。当骨骼的位置、方向等参数发生变化时,相关联的顶点也一同跟随其运动。而骨骼与骨骼之间的关系就像人体骨骼一样:当父骨骼发生变化时,子骨骼会发生同样的变化。因此在每一组骨骼中,除了根(Root)骨骼之外,每根骨骼都会有一个父骨骼。

骨骼与骨骼之间的节点称为Joint,或者说骨骼(Bone)是Joint之间的连接。他们在骨骼动画中可以看作是等价的概念。

这是因为所谓“骨骼”,无非是一系列的变换矩阵。从这一观点来看,Joint更适合描述骨骼动画中的“骨骼”。

接下来我们要讨论一些数学色彩稍多一点的内容,不过只要基本的线性代数的知识就足以应付了。

模型坐标系

我们在建模的时候,一般都在模型自身的坐标系中完成。在导出模型时,模型中顶点的坐标一般也以此为参考系导出;而当游戏引擎加载模型后,会使用场景管理器将其挂在场景图(Scene Graph)中合适的节点上。而在渲染时需要使用世界坐标,从模型坐标转换到世界坐标的过程将由游戏引擎来完成。

局部坐标系

大空间可能会由小的局部空间组成,如果我们为局部空间定义一个坐标系,就可以称其为局部坐标系,这是一个比较宽泛的、相对的概念。例如,在整个世界中,模型坐标系就是一种局部坐标系。如果在一个模型中,我们为更小的部分定义了一个坐标系(比如骨骼),那么它相对于模型坐标系来说,就是一个局部坐标系。

父坐标系

如果一个局部空间具有父节点,它的父坐标系就是指父节点的坐标系。

坐标变换

通常我们需要在不同的坐标系之间进行坐标变换。当一个点的坐标乘以一个变换矩阵时,我们可以认为这一个点在当前坐标系中移动了;也可以认为“点”本身没有动,是参考的坐标系移动了。也就是说,如果我们想要获得同一个点在不同坐标系下的变换矩阵,使用矩阵乘法即可获得结果。

3.骨骼动画原理

上文曾提到,模型的每一个需要移动的顶点,都通过与相应的骨骼绑定来实现。也就是说,只要我们记录下在每一帧中所有骨骼的位置、方向等信息,就可以计算出顶点的位置;如果我们在程序中每一帧都用这些信息更新顶点位置,就可以让模型动起来了。但是在此之前,我们还需要得到骨骼的“初始信息”。下面我们先暂时只考虑每个顶点仅绑定至一根骨骼的情况。

这是一种比较简单的实现方式,代价是很难进行合适的插值。下文将提供改进的方法。

Bind Pose

美工人员在绑定骨骼时,往往都在一种方便绑定的姿态下操作,这个姿态叫做Bind Pose。下图是一个比较典型的Bind Pose姿态(From cally):

bindpose

我们在导出模型时,顶点位置也往往使用模型处于Bind Pose姿态时的位置,而此时每根骨骼的姿态就是上文提到的“初始信息”。设BindPose姿态下某一根骨骼相对于模型坐标系的变换矩阵为Mb。当骨骼运动时,虽然顶点的坐标在变,但该顶点相对于所绑定的骨骼的位置并不改变(当仅绑定至一个骨骼时)。因此,对于每一个顶点,我们需要进行以下变换:

BindPose姿态下的坐标(模型坐标系)→局部坐标(骨骼局部坐标系)→当前帧坐标(模型坐标系)

首先我们先来解决第一步转换。我们曾记录了Bind Pose姿态时骨骼的姿态Mb,利用Mb,我们可以将局部坐标转换为BindPose坐标,即:

vL * Mb = vb

其中vL为局部坐标,vb为Bind Pose坐标。

如果读者不理解为何使用Mb可以将局部坐标转换为BindPose坐标,可以做以下的推理:

上文中曾提到,“当一个点的坐标乘以一个变换矩阵时,可以认为这一个点在当前坐标系中移动了;也可以认为‘点’本身没有动,是参考的坐标系移动了”。设想一个点从模型空间原点p(0, 0, 0),移动到BindPose姿态时骨骼所在的位置p’:

p * Mb = p’

那么如果另一点q处于骨骼的局部坐标系中,其坐标为q(0, 0, 0),希望将其变换为模型坐标系中的坐标q’,实质上就是将骨骼局部坐标系移动至模型坐标系,即

q * Mb = q’

因此如果我们想将Bind Pose坐标转换为骨骼局部坐标,需要以下公式:

vb * Mb-1 = vL

也就是说,我们只需要记录Mb-1即可。在第二步转换中,变换矩阵就是骨骼在当前第i帧的位置Mi(模型坐标系),即

vL * Mi = v

因此我们所需要的信息只有两个:1.每根骨骼的BindPose矩阵的逆。2.每根骨骼在每一帧的矩阵。(它们都是相对于模型坐标系的。)只要有这两个信息,我们就可以计算出每个顶点在每一帧的位置了。

在这一小节结束之前,还有一点需要补充:所谓Bind Pose姿态,不过是一个“基准”。其实任何一帧的姿态都可以成为“基准”,即只要在导出时模型的顶点位置与骨骼的变换矩阵在同一姿态下即可。

4.骨骼动画的插值

上述方法很难对动画进行合理插值,这就失去了骨骼动画的一大优势。其原因在于我们所使用的M矩阵是相对于模型空间的。如果仅依此对矩阵进行插值,有时很难的到合理的结果,容易出现错位、交叠等现象。

解决这个问题的办法是使用相对于父骨骼的矩阵Mr(即相对于父坐标系)。当前骨骼通过Mr得到父坐标系下的坐标,其父骨骼再通过它的Mr得到父亲的父坐标系下的坐标……直到计算到相对于模型坐标系的坐标。如下图(From* Game Engine Archtecture*):

matrix

即上文中的公式将变成:

vL * Min * Mi(n-1) … * Mi0 = v

由于此时每个骨骼将相对于父骨骼进行运动,因此插值结果将更加合理。不过变换矩阵的插值算法超出了本文的范围。

5.将顶点绑定至多个骨骼

当需要将顶点绑定至多个骨骼时,需要为每个骨骼设置一个权重,代表该骨骼对该顶点的影响程度。计算时每个绑定的骨骼的都按照上述计算方式进行计算,最后根据权重计算最终的顶点坐标即可。

6.总结

本文简要介绍了蒙皮骨骼动画的基本技术原理。下一篇文章将介绍如何编写3dsmax插件导出骨骼动画,并使用OpenGL在我们的程序中加载。