使用骨骼动画

来源:互联网 发布:淘宝老客户管理软件 编辑:程序博客网 时间:2024/04/30 06:13
使用骨骼动画

 一·概览

       当夜幕包围了我们的英雄,我不津微微一笑。“那些笨蛋永远也搞不明白会发生什么”,我对自己说。我轻轻地推动遥杆,英雄缓缓前行。在一个角落里,我按下一个按钮。突然,他攀援上了墙头,在一阵安静地等待中,英雄慢慢地检查了他的夜视镜,武器,子弹……

       这一切的一切,都实时地发生着。这其中角色动作的改变没有任何跳跃或中断。跑,爬,攀援或是漠不关心地调整,这些动作都是平滑地逐一进行。在我的脑海中, 我可以清晰地看到角色内在的骨骼结构如何使当地配合他的动作。角色的网格配合着骨头表现着每一祯细微的差别,无论是他皮肤的纹理还是肌肉的皱褶。 

       通过骨骼系统,你可以创建这些动画——这大概是你的项目中最酷的技术。这套技术,比它刚刚问世时好用得多。对于Tom Clancy的Splinter Cell这种游戏向世界展示的骨骼动画技术而言,你绝对不希望跳过这一部分的学习。这一章会告诉你这一切如何开始,也就是从如何使用骨骼层级结构到使用皮 肤网格贴图。

 

二·获得骨骼动画

骨骼动画——在这个主题中你将受到的冲击比任何一部恐怖电影恐怕都要多,当然,前提是你是一个像我一样对于技术抓狂的游戏程序员。

骨骼动画很快就成为了游戏程序员们的选择,因为它处理起来很快而且效果让人瞠目结舌。你可以使用它为角色的每一个细节编制动画。它可以让你操纵角色身体的每一个细节,从皱纹到肌肉的隆起。你可以通过操纵每一个节点、每一块骨头和肌肉来改变角色网格数据的形态。 

可以这样考虑骨骼动画:你的身体是一个网格,下面是一套完整的骨骼系统。当你的肌肉推、拉、扭曲你的肌肉时,你的身体形状随之改变。现在不要认为是肌肉拉动了你的身体,而是认定是骨骼控制了你全身的变化。 

如果你抬动你的胳膊绕着肩旋转,会引起你整个胳膊转动而且皮肤改变了形状。你的身体(就是网格)通过骨骼系统从而改变了形状。骨骼系统就是这样工作的,内在的骨骼系统改变节点和网格的原来的方向和位置,从而适应身体姿态的变化。 

如你所见,在处理骨骼动画的时候有两个独立的部分要分别处理:骨骼结构和网格贴图。现在我们仔细地看看如何让他们好好地协同工作吧——先从骨骼结构开始。

三·使用骨骼结构和骨骼层级

骨骼结构,如你所想,是由一群连在一起的骨头构成的层级结构——顾各层级。有一块骨头,我们称之为根骨,成了访问整个骨骼结构的入口。其它的骨头都是直接的或者间接的连在这块根骨上。

“骨头”指的是一个引用对象结构。(D3DXFRAME结构,或者Frame模版,参看.x 文件)如果你察看D3DXFRAME结构,你会在层级结构中清晰地发现链表指针pFrameSibling和pFrameFirstChild。 pFrameSibling志珍把另一块骨头作为这块骨头的同级骨骼。而pFrameFirstChild则把另一块骨头作为这一块骨头的“子骨头”,即 下层骨头。

一般来说,都是使用3D模型包来为你的项目创建骨骼层级结构。把结构导出到.x文件是一个非 常好的注意。微软已经发布了对于3D Studio Max和Maya的导出插件,使得你可以轻松地把骨骼动画数据导出到.x文件中,而许多其它的建模工具也都提供这一功能。现在我们假定你确实是使用.x文 件作为骨骼结构的存储媒介的。

在保存了骨骼层级结构的.x文件中你会发现很多东西:首先,最重要的是你会看到Frame模版。它是你的骨骼层级数据的载体。如果你弄了一个简单的骨骼层级结构,就像下图所示的,你就会得到一个右边那附图显示的那样的骨骼树结构。

同时你也应该看到有一个标准的网格数据对象被内嵌到了骨骼数据对象层级结构中。这个网格对象 包含了有关骨骼动画对象和骨骼结构中所使用的骨头的有关信息。没错,骨骼结构数据对象和网格对象都含有骨骼结构。区别在于,骨骼结构数据对象定义了确切的 骨骼层级结构,而网格对象定义了哪个结构对象描绘了哪块骨头。

现在的情况是,骨骼数据是一盘散沙。由于骨骼受到层级结构支配,我们就有必要集中精力搞明白骨骼层级结构了。你首先要读取并构架起骨骼层级结构以备后用。继续看下去,我们看看如何从.x文件中读取各个层级结构。

1 从.x文件中读取层级 

常言说,别打死路(我又何苦如此残忍?),但是我打算快速地回顾一下如何从.x文件中读取骨骼层级。尽管第三章详尽地讲述了如何使用.x文件,以及读取骨骼层级,我还是想在捋一次,并且使用清晰地结构来保存骨骼层级。

一般来说,我们使用D3DXFRAME结构或者D3DXFRAME_EX结构来保存骨骼层级结构。正如这章我早先提到的,D3DXFRAME结构包含两个指针,用来创建骨骼层级链表——pFrameSibling何pFrameFirstChild指针。

从根结构对象开始,迭代遍历.x文件中的每一个数据对象。当你遇到一个骨骼对象时候,就把它 按照前一个骨骼的子骨骼或者同级骨骼连上去。继续遍历.x文件,直到所有的骨骼对象都被读进了链表。在这个例子中,我们使用D3DXFRAME_EX结构 建立骨骼层级结构保存骨骼数据。

实际上第三章包含了更多的分析.x文件的信息。这里我们要采用一种更简单的说明方式。基本上,你打开一个.x文件,遍历每一个数据对象,而你需要为所遇到的每一个Frame对象创建D3DXFRAME,或者D3DXFRAME_EX对象并且把它接入层级系统。

要处理.x文件,你可以构建一个类,让它帮你做绝大多数工作。你要重载这个类的ParseObject函数,这个函数可以让你访问每一个数据对象。还是那句话,第三章详细介绍了这个类的使用方法,不再重述。

现在我们来简要地看一下用来枚举每一个数据对象的ParseObject函数:

如果你还没有读过第三章(那我就真的为你羞愧万分了),有些代码读起来可能有点晕。大概地 说,ParseObject函数用于枚举每一个数据对象。在ParseObject函数内部,我们检查当前枚举的数据对象的类型(通过GUID)。如果是 一个Frame类对象,你就要分配一个Frame结构并且读入名称。

接下来,你把这个frame连接到骨骼层级结构中。这里看起来有一点奇怪和陌生:CXFrameParser类维护两个指针:一个是正在被创建的“根骨”,另一个是指向数据对象的指针。数据指针一直指着最后被架载的Frame数据对象。

在刚刚开始分析.x文件的时候,数据指针data被设置为空值NULL,意味着它并没有指向 任何被加载的数据。当你读到你个Frame对象并保存到一个frame结构的时候,你会检查data指针是否指向了另一个frame结构对象。如果没有, 就会假定当前的frame是一个根骨级的骨骼。而如果data指向了一个什么别的frame数据对象,就说明当前骨骼是那块data指着的骨骼的子骨骼。

正在枚举的frame是一个同级骨骼还是一块骨骼的子骨骼对于你创建骨骼层级来说是一件非常 重要的事情。同级骨骼被连接在pFrameSibling指针中,而子骨骼则被连接在pFrameChild中。一旦frame被装载,data指针就会 调整到新的骨骼,或者回到一个同级骨骼。最后,所有的骨骼都会以同级骨骼或子骨骼的身份被连到一起。

另一件会引起你注意的事情,应该是关于加载frame的变换矩阵——FrameTransformMatrix模版——的代码。一个framtransformMatrix对象是一个典型的内嵌在frame对象中的对象。这个对象定义了正在加载的骨骼的初始方向。

对于骨骼动画而言,骨骼变换矩阵定义了骨骼结构的初始状态。比如,一个站着的骨骼结构可能是 躯干站立,两臂舒展。但是,动画都是基于不同的角色站立姿势。或许胳膊耷拉在两旁而两腿弯曲。与其在保存.x文件之前建模程序中重定向所有的骨骼和定点以 获得合适的位置,不如改变一下骨骼的变换矩阵。这样,所有的骨骼动作都会基于这个姿势。在你操纵骨骼方向的时候,这一切会变得很明晰。因此,我把这个主题 留到稍后处理。现在只要知道,每一个frame内部都有一块用于存储变换矩阵的空间——D3DXFRAME:: TransformationMatrix。

该说的都说完了,你的骨骼层级已经被加载了。当然,根骨头被存在m_RootFrame中,并且D3DXFRAME_EX对象构成了骨骼层级链表。现在你应该抓住那个指针,为它分配你的程序中要使用的东西了。做完了这些之后,我们可以开始研究给骨头定向的问题了。

2 修改骨头方向

在装载了骨骼层级之后,你可以操纵它了。要操纵骨头,你需要先找到它的位置,也就是得到指向你要操纵的骨头的指针。因此你需要创建一个搜索函数,递归查找一个具有特定名称的骨头。一旦发现了它,你就可以通过返回的指针直接操纵它的变换矩阵了!搜索函数大概就是这个样子:

注意:当然你可以对骨头的矩阵做任何处理,但是通常我们只是旋转。为什么呢?比如举个例子,当你弯曲胳膊的时候,胳膊旋转了。你如何解释你把你的胳膊“平移”出去了呢?!估计是你的胳膊离开了你的身体——你大概不希望这种事情发生吧!

如果你想移动整个角色的网格,只要移动根骨头就好了。其他的所有骨头都会继承那个变换矩阵。目前,应该使用世界矩阵移动贴图网格对象。

 

假象你想用FindFrame函数找到一块叫做leg的骨头,你应该提供骨头的名字和一个根骨的指针,就像下面这样:

D3DXFRAME_EX *Frame = FindFrame(pRootFrame, "leg");

if(Frame)

{

       //设定变换矩阵,比如这里旋转一下

       D3DXMatrixRotationY(&Frame->TransformationMatrix, 1.57f);

}

 

3 更新层级结构

 

在制定了骨骼的方向之后,你需要更新整个骨骼链以备后用。即便没有改变骨骼的方向,你也需要更新骨骼链,因为在渲染之前还需要设定若干变量。

 

在更新骨骼连的时候,你需要你需要让变换矩阵沿骨骼链继承下去。从根骨开始,你要把骨头的变 换矩阵用到frame的复合变换矩阵。这块骨头的变换矩阵还要被传送并且榜定到所有的同级矩阵。从那里,你计算出来的复合矩阵又被传到了根骨的每一个子骨 头。这个过程沿着骨骼链传播。

 

尽管最初这东西很难理解,你可以这样考虑这个过程:看下面的图的骨骼链,从root开始,乘上它的变换矩阵,这个矩阵指明了根骨在世界坐标的位置。

如图所示,复合矩阵从根骨传递到所有的子骨头,每个骨头的转换矩阵都被复合了前面的转换矩阵。最后的结果被传递到了最下层的骨头。但是,基于这种方式计算变换矩阵是非常恐怖的,以此我们需要一些别的方法。

 

最简单的更新骨骼链的方法是建立一个地归函数,从而将一个给出的变换矩阵榜定到整个骨骼链上。这样,这个矩阵被一级一级传下去,并且复合在骨头的子骨头上。看看这个函数:

如你所见,UpdateHierarchy函数使用一个D3DXFRAME_EX对象作为第一个参数。这是当前正在处理的骨骼。你只要调用UpdataHierarchy一次,提供一块根骨,这个函数就会递归调用自己,直到骨骼链遍历完成。

 

注意到,第二个参数——matTransformation。这个参数是要复合到当前骨头的 变换矩阵的变换矩阵(晕)。默认情况下,这个矩阵指针定位NULL,意味着你使用的是一个恒等的矩阵。在当前骨头的变换矩阵复合好了之后,这个新矩阵被传 到了它的子骨头上,同样是设置为matTransformation。

 

注意:如果你已经读过了第一章,你应该注意到UpdateHierarchy函数已经榜定在了D3DXFRAME_EX类中。所以你可以直接使用根骨的::UpdateHierachy成员函数!回到第一章去看看关于这个函数的细节吧!

 

我刚刚提到过,你只要调用UpDataHierarchy函数。不要为第二个参数提供变换矩阵——那应该留给函数递归的时候用。如果你提供了一个变换矩阵,你会移动整个网格!这就和设定世界矩阵来定位和定向网格以渲染它是一样的了。

 

UpdateHierarchy(pRootFrame);

 

现在你多少明白了骨骼结构和如何使用骨骼,我们可以看看第二部分——表面贴图对于骨骼动作变化的适应。

原创粉丝点击