最近有人问我怎样实现骨骼动画,于是我就想起了我以前写的这篇文章,贴上来给大家看看。
一、文章编写目的
写这篇文章,是给程序员看的。目的在于给程序员介绍骨骼动画的原理、数据结构和程序实现的粗略方法。
骨骼动画的应用面很多,主要用在3D角色动画,不过现在也很多人用于2D动画。下面的内容不会直接的把程序列出,只会阐述原理,关键的步骤是使用矩阵做坐标系变换。原理明白之后,不管2D或者3D应该都能自己编写。
二、什么是骨骼动画
传统的动画,一般是对一个物体对象进行位移、旋转、缩放、变形,然后把关键帧的信息记录下来,在播放的时候按照关键帧时间对物体对象进行位移、旋转、缩放、变形,并在关键帧与关键帧之间做插值运算。
骨骼动画的特点是,需要做动画的物体对象本身不记录位移、旋转、缩放、变形信息,而是通过了第三方的“骨骼”物体记录动画信息,然后物体对象本身只记录受到骨骼物体影响的权重。在播放的时候,通过骨骼物体的关键帧和物体对象记录的权重,让动画重现。
更多内容,请访问Unity3D技术社区【狗刨学习网】http://unity.gopedu.com
三、骨骼动画的好处
相对于传统动画,骨骼动画似乎多了2个记录:骨骼物体和权重。按道理来说,这种骨骼动画的资源容量和运行效率应该不如传统的动画,那为什么还要做骨骼动画呢?
好处有以下几点:
1、骨骼动画是影响到顶点级别的动画,而且可以多根骨骼根据权重影响同一个顶点,不论是2D或者3D使用,都可以让动画做得更丰富。
2、做到了对象和动画分离,我们只需要记录了物体对于骨骼的蒙皮权重,就可以单独的去制作骨骼的动画,在保证蒙皮信息和骨骼信息一致的情况下,还可以多个物体之间共享动画。
3、相对于2D的逐帧动画大大的节省资源容量。
4、3D角色动画离不开骨骼动画。
四、美术层面理解骨骼动画
由于骨骼动画一般是由美术人员,更准确的是由动画师来制作的,所以相对于程序员来说,美术人员接触的机会比较多。这篇文章是给程序员看的,不过在从数据级别操作骨骼动画之前,最好还是先了解一下美术层面制作骨骼动画的方法。至于2D还是3D的情况下做骨骼动画,其实原理是一样的,只是2D时少了几个点,然后一个轴固定值了而已。
这里以3DsMax来举例子。需要制作一个骨骼动画,需要这几样东西:
1、网格物体
2、骨骼物体
3、蒙皮修改器
4、关键帧
网格物体:
需要做动画的对象物体,如这个例子里面我们需要让这个长方体翘起来
骨骼物体:
严格的骨骼对象物体,3DsMax有2种,一种是Bone,一种是biped。其实所有的物体都可以作为骨骼,包括了多面体网格物体、辅助物体等,都可以作为骨骼使用。不过为了便于一般操作习惯和导出数据方便,我们都只用Bone和Biped这两种。
蒙皮修改器:
3DsMax有2种蒙皮修改器,Skin和Physique。作用都一样,都是给网格物体的顶点赋予权重。也没有谁好谁坏之说,看个人使用习惯。不过由于大部分的模型导出插件都不支持Physique蒙皮修改器,所以一般来说,都是使用Skin。
蒙皮修改器可以使用范围框、笔刷或者逐个点来调节权重,不过本质都是一样的,最后得到的是几根骨骼相对于某个顶点的影响权重。
上图中的顶点受到骨骼bone01的0.25的影响,受到bone02的0.75的影响。这种情况下,该顶点虽然同时受到2根骨骼的影响,但bone02对它的影响会比较多一点。
正常的情况下,一个顶点受到多根骨骼影响的权重加起来应该等于1。
关键帧:
关键帧信息包括了
l 时间
l 位移旋转缩放的值
l 过渡插值的方式
注意的是,软件操作上打了关键帧的物体才会动,不打关键帧的物体不会动或者跟随着父级物体动。所以很多动画师对一些由于不需要动的物体不打关键帧的。但由于每种动画导出插件的处理不一样,有些插件的编写作者也是忽略了这种情况,而导致了在动画解析的过程中缺少了部分的数据,导致动画播放不正常。
五、程序层面理解骨骼动画
相对于美术层面,程序方面需要以下的几个数据:
1、顶点索引数据
相对于美术只用关系网格物体的原始的位移旋转缩放,程序关心的是网格物体的每一个顶点的位置坐标,还有顶点之间组成网格的索引顺序。所谓的原始,指的是物体在没有进行蒙皮之前的位置。
如果是做2D,那么数据就会变成每个面片的四个顶点的坐标,而索引就要自己用算法去生成。当然索引只是在3d渲染时才用到,如果是纯2d,你可能只关心四个顶点的位置,然后绘制出面片。
由于之后一直都需要针对每一个顶点去储存和提取数据,所以在一开始的时候,就要把这些顶点的顺序固定下来。
2、骨骼物体数据
骨骼物体我们不会关心他是什么类型,体积有多大,或者由多少个顶点组成。我们只关心它的位移旋转缩放的原始坐标,还有父子链接的关系。这个原始坐标需要和网格物体的原始状态保持一致,即没有蒙皮和做动画之前,骨骼的位置。由于骨骼通常是很多根的,所以在记录的开始,也必须先给骨骼都排序。
对于2D来说,骨骼有可能并没有实质性的形象,你可以用一个矩形或者一个线条做代表,最后需要导出的只是该对象的坐标,还有父子链接的关系。
父子链接关系:图中有2根骨骼。其中父物体发生位移旋转,子物体会跟随父物体做位移旋转。但子物体做位移旋转的时候,父物体是不会受到影响的。
这里会涉及到2个坐标系的概念,一个是世界坐标,一个是局部坐标。
如果一个物体没有父物体,也就是说它的父物体就是“世界”,那么它的当前坐标就是世界坐标;
如果一个物体有父物体,那么它除了有世界坐标以外,还有一个相对于父物体的相对坐标。
例如:
父物体的世界坐标是(1,0,0),子物体的世界坐标是(0,1,0),那么子物体相对于父物体的局部坐标就是(-1,1,0)
3、每一个点的蒙皮权重
之前我们记录顶点信息和骨骼信息时,都是已经排好序的,所以我们在记录蒙皮权重的时候,就可以只是记录各自的序号,还有对应的权重。一个顶点原则上是可以受到无限多根骨骼的影响,不过在实际的应用中,为了不让计算过于复杂,一般一个顶点最多只会受到4根骨骼的影响。
4、关键帧数据
根据记录骨骼坐标的坐标系不同,可以选择记录每一根骨骼的绝对坐标,也可以记录有动画的骨骼的相对坐标。
根据是否计算插值,可以选择记录每一帧的信息,也可以选择只记录关键帧的信息,还有关键帧的插值方式。
之前我们介绍了绝对坐标和相对坐标,如果是记录世界坐标,那是最准确的,但由于每一根骨骼的每一帧都需要去记录,不管它是否做过动画,这样动画的数据量会比较大。如果记录的是相对坐标,那么如果在子物体没有做动画的情况下,它就不需要记录,而是通过父物体的坐标来计算出子物体的世界坐标。这样做可以让导出的数据容量很小。不过这会增加解析时候的运算量。使用哪一种方式记录关键帧,需要根据自己的项目实际情况来决定。
五、骨骼动画的计算原理
1、4*4矩阵的概念
对于骨骼关键帧的记录,我们需要记录位移(xyz)、旋转(通常用四元数xyzw)、缩放(xyz)这么多个数据。我们计算的时候也可以逐个点去分别计算他们的位移旋转缩放来做动画。不过骨骼动画一般会涉及到坐标系的转换,所以我们通常会都用矩阵来做运算。
一般的矩阵运算都是3*3矩阵,但在做3D方面的运算时,通常会把矩阵扩展为4*4矩阵。具体的做法请参考矩阵的基础知识。大概过程是,先通过旋转和缩放算出3*3矩阵,然后再增多一个维度填上位移坐标。
根据矩阵左乘和右乘的不同,你的矩阵可能不一样,下面的公式是以左乘为基础的。
不同的引擎不同的作者,可能使用不同的矩阵相乘规则,由于矩阵相乘是不满足交换律的,也就是AB!=BA,所以一开始必须先搞清楚当前的引擎矩阵究竟是左乘还是右乘的,还有坐标系是用左手坐标系,还是右手坐标系。形式转换经常是错误的根源。
经过位移旋转缩放的计算,或者直接从3D软件里面就导出矩阵,我们得到的4*4矩阵应该是这样的
左乘:
r11 r12 r13 0
r21 r22 r23 0
r31 r32 r33 0
tx ty tz 1
右乘:
r11 r21 r31 tx
r12 r22 r32 ty
r13 r23 r33 tz
0 0 0 1
其中r3*3是旋转缩放计算的矩阵,tx、ty、tz是位移
2、坐标系转换概念
之前说过,骨骼有世界坐标和局部坐标的分部。其实所有东西都有绝对坐标和相对坐标的概念。
拿顶点和骨骼来说,顶点本身有世界坐标,也有相对于骨骼的相对坐标。
关于绝对坐标系和相对坐标系的转换,请参考3D图形数学里面的坐标系转换。我只说简单的原理。
假设有:物体A和物体B,A的世界坐标是Va世界,B的世界坐标是Vb世界
现在我们需要把A的坐标转换成相对于B的相对坐标,记作Vab。
那么我们需要构造一个矩阵M世界-b,
Vab = Va世界*M世界-b。
其中M世界-b是根据Vb世界求出来的,一般是Vb世界的逆矩阵。
反过来,如果制定了A在B的相对坐标,也知道了B的实际坐标,我们需要求回A的世界坐标。那么应该是
Va世界= Vab*Mb-世界。
其中Mb-世界矩阵就是M世界-b的逆矩阵。乘以逆矩阵的几何含义,就是把之前做过的变换取消掉。
一定要先把这个矩阵转换坐标系的概念搞清楚,这其实就是整个骨骼动画的原理关键。其中的A就是物体的顶点,B就是骨骼。
3、骨骼动画原理
前面废话了这么多,终于到核心部分了。
我们先从最简单的情况考虑。
假设我们的模型只有一个顶点,骨骼也只有一根,这个顶点完全受这根骨骼的影响。
红色点是顶点,蓝色的线是骨骼,这里把骨骼画成线只是为了便于叙述,其实骨骼是没有长度概念的。
我们旋转了骨骼,由于顶点完全受骨骼影响,所以顶点的坐标也发生了变化。
那么我们怎样求出顶点在动画之后的坐标呢?
通过观察,顶点在动画前和后后,虽然实际坐标发生了变化,但是相对于骨骼的相对坐标是没有发生变化的。只要知道了相对坐标,再通过动画后骨骼的坐标,就能通过转换坐标系,得到动画后顶点的世界坐标了。
那么,我们可以这么做:
设:
顶点的动画前世界坐标为:V世界0
顶点在动画后世界坐标为:V世界1
骨骼的动画前世界坐标为:B世界0
骨骼在动画后世界坐标为:B世界1
然后通过B世界0可以计算出从世界坐标转换到骨骼相对坐标的:M0世界-b
通过B世界1可以计算出从骨骼相对坐标转换到世界坐标的:M1b-世界
然后,我们要求出V世界1
首先求出顶点相对于骨骼的相对坐标Vb:
Vb = V世界0*M世界-b
然后当运动之后,我们就可以求得
V世界1=Vb*M1b-世界
那么整个过程就是
V世界1 = V世界0*M世界-b*M1b-世界
看似过程有点复杂,其实Vb是固定的,我们甚至可以在导出数据的时候就先算好了。到了真正需要做动画的时候,我们只需要再知道一个B世界1,就足够我们算出当前的顶点应该在的位置了。
我们再加大复杂程度:
假设顶点还是一个,但骨骼变成了2根,顶点受a骨骼0.75权重的影响,受b骨骼0.25的影响
我们需要做的和前面一样,虽然骨骼变成两根了,但我们可以先忽略了权重,分别求出V世界1a和V世界1b。
之后的事情就很简单了:
V世界1 = V世界1a*0.75+V世界1b*0.25。
如此类推,把权重标记成W,就变成了
V世界1 = V世界1a*Wa+V世界1b*Wb+V世界1c*Wc+V世界1c*Wc……
4、骨骼动画的整个工作流程:
1.导出原始数据,包括顶点原始位置、骨骼原始位置、蒙皮权重和骨骼关键帧。
2.解析数据,为每一个顶点记录下相对于影响它的骨骼的相对坐标,和该骨骼对它的影响。然后把骨骼关键帧解析成每一帧的4*4矩阵,并按照时间储存起来。
3.骨骼动画开始播放,先获得该时间点的所有的骨骼的矩阵,用缓存器存起来已备接下来所有的点都能访问。然后每个顶点根据第2步解析的影响它的骨骼的编号获取相应骨骼的坐标,配合权重计算出该顶点真实的世界坐标。
4.把所有顶点按照第3步都计算完毕,然后渲染出来,整个角色的动画就出现了。
更多内容,请访问Unity3D技术社区【狗刨学习网】http://unity.gopedu.com