【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一)

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

 

 

 本系列文章由zhmxy555(毛星云)编写,转载请注明出处。  

 文章链接: http://blog.csdn.net/zhmxy555/article/details/8832812

 作者:毛星云(浅墨)    微博:@浅墨_毛星云      邮箱: happylifemxy@163.com


 

这是答应大家的讲解骨骼动画的文章的N部曲的第二篇。这篇文章里,我们对现行的三种模型动画技术进行了概述,然后对X文件构成进行了详细的剖析,最后放出了骨骼动画的第一个示例程序,载入了《诛仙》中陆雪琪非常优雅的”剑舞“动画。伊人在漫天雪花之中翩翩剑舞,非常有意境:)。

先上几张截图来一睹陆雪琪舞剑的风采吧:

 





示例程序源代码在文章末尾提供下载。

好吧,咱们开始正文。





一、模型动画概述


我们通常说的模型动画,其实有三大类。对于大部分模型动画的实现原理基本上是异曲同工的,也就是提供一种机制,用于描述三维模型中各个顶点的位置随着时间的变化。

通常有三种模型动画的实现方法,他们分别是:

 

1.关节动画

2.渐变动画

3.骨骼蒙皮动画

 

接着我们分别来做一下简介。

 

1.关节动画


关节动画的思想是把角色分为若干个独立的部分,每个部分都对应了一个独立的网格模型,并且这些网格模型按照角色的特点组成一个层次结构。

 

网格模型中保持了最初状态的顶点坐标和他们的位置等等数据,还有一系列后续时刻所对应的运动矩阵(一般而言,为了节省存储的空间,不会去保存每时每刻的顶点数据)。

关节动画的优点是它的占用空间很小,并且利用关键帧的插值运算可以实现复杂的动画效果。但是,它的缺点是角色组成部分之间的交接处容易产生明显的接缝,显得很假。

 

 

2.渐变动画


渐变动画的思想是将角色通过一个完整的网格模型进行描述。而在模型动画的序列中,通过关键帧去记录网格模型中每个顶点的新位置(也就是相对于原位置的改变量)。这种方式只需要在关键帧之间进行插值运算从而改变网格模型中各个顶点的位置,就可以实现模型的动画效果来。

而与上面我们介绍到的关节动画相比,渐变动画使用了单一而浑然一体的网格模型,使实现的角色更加真实,而且也不会产生像关节动画那样尴尬地要面临着接缝问题。同时,因为渐变动画没有使用层次模型,所以在取得网格模型中各顶点位置时的计算量比较小。但是问题当然也是有的,那便是这种方式要保存一系列时刻网格模型中相关顶点的位置,所以占的存储空间是非常大的,而且也比较死板,灵活性很差。

 


3.骨骼蒙皮动画

 

万人迷来了。:D

我们在游戏程序中通常会采用骨骼蒙皮动画来制作出动作效果。

我们来看看它到底有哪些迷人的特质。

 

上面我们刚讲到关节动画和渐变动画,可以这样理解,他们是两个极端:

关节动画,占用空间小,表现力差。

渐变动画,占用空间大,表现力好。

 

而具有成功人士特质的目前使用最广泛的三维动画技术“骨骼蒙皮动画”,自然会取其精华,去其糟粕,吸取它们的优点,摒弃它们的缺点,最后糅合折中而成的属性便是——占用空间小,表现力好。

好了,我们来看一下骨骼蒙皮动画的具体原理。

骨骼动画的实现原理是仿照人体的运动方式,其中将角色由一种称作“蒙皮(skin)”的单一网格模型和按照一定层次组织起来的“骨骼(Bone)”组成。骨骼层次仿照关节动画的组织结构将角色组织成一个层次结构。而相邻的骨骼之间通过关节相连,他们之间通过做相对运动来实现特定的动作效果,从而就实现了不同的模型动画效果。

而皮肤网格模型与骨骼相关联,用于提供绘制动画所有需要的几何模型(比如顶点、法向量等等),还有纹理和材质等一些信息。组成皮肤网格的每个顶点都会受到一个或者多个骨骼的影响,而每个顶点受到多个骨骼影响的程度通过权值(Weight)确定。通过计算每个顶点受到不同骨骼对他们影响的加权和,就可以得到这个顶点在运动过程中所处的实际位置。

 

另外需要注意的是,骨骼蒙皮动画通过关键帧确定骨骼的位置、朝向等等一些信息。通过在动画序列(Animation Set)中相邻的两个关键帧之间进行插值运算,就可以确定某一时刻各骨骼所处的新位置和新朝向等一些额外信息。

 

以上的各项“领袖气质”,注定了“骨骼蒙皮动画技术”会引领潮流,力压有明显缺陷的“关节动画技术”和“渐变动画技术”,成为各类实时动画应用中使用最广泛最核心的动画技术。

 

 


二、对X文件格式的分析


想好好掌握骨骼蒙皮动画技术的使用,首先还得从源头处了解X模型文件的构成,看看这些模型到底是徒有其表的模型,还是除了表面展现出来的模型效果之外,还有更深层次的东西——动画序列(Animation Set)。

 

我们先来对普遍的X文件来一个分析。

我们知道X文件格式是微软定义的3D模型文件格式,三维建模软件3ds Max、Maya制作出来的三维模型,可以很容易地转换为X格式。X格式在我们学习三维游戏编程的初期用起来是非常方便的。

我们如果用记事本打开X文件的话,会发现其中用大量的代码和数字,定义了包括网格的顶点、纹理、动画、材质以及其他的一些内容。X文件是以模板驱动的,也就是说,它存储数据的格式是基于模板的,这使得X文件具有结构自由、内容丰富、易应用和可移植性高等优点。- -怎么觉得自己在裹空。

好了,不裹空了,我们来仔细看一下这些所谓的基于模板的X文件定义方式到底卖得什么药。要想在Direct3D程序中灵活自如地使用网格模型,应当深入理解.x文件格式。如果以后使用physX,bullet等做物理模拟碰撞检测的话,也因为了解X文件的格式而手到擒来的。

 


1.首部(header)


每个.x文件都是以一个首部(header)来开头的。对应于我们这次使用的《诛仙》中的陆雪琪的X文件,用记事本打开之后(更简单的方法是直接把X文件拖到Visual Studio中打开,因为有行号,看起来更加舒服),第一句就是如下所示:

 

xof 0303txt 0032


这是比较常见的一种X文件的首部。其中,xof代表这是一个X文件。接下来,版本号由两部分组成,前两位为主版本号,后两位为次版本号,那么0303就代表X文件是使用3.3版本的模板。txt代表接下来的X文件是用文本文件(text)格式存储的,而不是二进制(bin)。最后的0032是浮点数的位数是32位。

 

另一种比较常见的X文件的首部是这样的:

 

xof 0303bin 0064

 

通过上面的讲解,我们可以很容易地推算出,它表示3.3版本的二进制文件格式储存的64位浮点数的X文件。

 



2.模板定义部分


上面我们讲到了X文件存储数据的格式是基于模板的。为了大家印象更加深刻,首先,贴出这次使用的《诛仙》中陆雪琪人物模型的X文件的0~145行“代码“:

 

xof 0303txt 0032template ColorRGBA { <35ff44e0-6c7c-11cf-8f52-0040333594a3> FLOAT red; FLOAT green; FLOAT blue; FLOAT alpha;} template ColorRGB { <d3e16e81-7835-11cf-8f52-0040333594a3> FLOAT red; FLOAT green; FLOAT blue;} template Material { <3d82ab4d-62da-11cf-ab39-0020af71e433> ColorRGBA faceColor; FLOAT power; ColorRGB specularColor; ColorRGB emissiveColor; [...]} template TextureFilename { <a42790e1-7810-11cf-8f52-0040333594a3> STRING filename;} template Frame { <3d82ab46-62da-11cf-ab39-0020af71e433> [...]} template Matrix4x4 { <f6f23f45-7686-11cf-8f52-0040333594a3> array FLOAT matrix[16];} template FrameTransformMatrix { <f6f23f41-7686-11cf-8f52-0040333594a3> Matrix4x4 frameMatrix;} template Vector { <3d82ab5e-62da-11cf-ab39-0020af71e433> FLOAT x; FLOAT y; FLOAT z;} template MeshFace { <3d82ab5f-62da-11cf-ab39-0020af71e433> DWORD nFaceVertexIndices; array DWORDfaceVertexIndices[nFaceVertexIndices];} template Mesh { <3d82ab44-62da-11cf-ab39-0020af71e433> DWORD nVertices; array Vector vertices[nVertices]; DWORD nFaces; array MeshFace faces[nFaces]; [...]} template MeshNormals { <f6f23f43-7686-11cf-8f52-0040333594a3> DWORD nNormals; array Vector normals[nNormals]; DWORD nFaceNormals; array MeshFace faceNormals[nFaceNormals];} template MeshMaterialList { <f6f23f42-7686-11cf-8f52-0040333594a3> DWORD nMaterials; DWORD nFaceIndexes; array DWORD faceIndexes[nFaceIndexes]; [Material<3d82ab4d-62da-11cf-ab39-0020af71e433>]} template Coords2d { <f6f23f44-7686-11cf-8f52-0040333594a3> FLOAT u; FLOAT v;} template MeshTextureCoords { <f6f23f40-7686-11cf-8f52-0040333594a3> DWORD nTextureCoords; array Coords2d textureCoords[nTextureCoords];} template XSkinMeshHeader { <3cf169ce-ff7c-44ab-93c0-f78f62d172e2> WORDnMaxSkinWeightsPerVertex; WORDnMaxSkinWeightsPerFace; WORDnBones;} template SkinWeights { <6f0d123b-bad2-4167-a0d0-80224f25fabb> STRING transformNodeName; DWORD nWeights; array DWORD vertexIndices[nWeights]; array FLOAT weights[nWeights]; Matrix4x4 matrixOffset;} template Animation { <3d82ab4f-62da-11cf-ab39-0020af71e433> [...]} template AnimationSet { <3d82ab50-62da-11cf-ab39-0020af71e433> [Animation<3d82ab4f-62da-11cf-ab39-0020af71e433>]} template AnimationOptions { <e2bf56c0-840f-11cf-8f52-0040333594a3> DWORD openclosed; DWORD positionquality;} template FloatKeys { <10dd46a9-775b-11cf-8f52-0040333594a3> DWORD nValues; array FLOAT values[nValues];} template TimedFloatKeys { <f406b180-7b3b-11cf-8f52-0040333594a3> DWORD time; FloatKeys tfkeys;} template AnimationKey { <10dd46a8-775b-11cf-8f52-0040333594a3> DWORD keyType; DWORD nKeys; array TimedFloatKeys keys[nKeys];}


我们可以看到,除了第一行是首部(header)以外,其他的144行全是一堆template括起来定义的某样内容,而某个template定义的内容互不相干,结构还是非常清晰的。

这就像我们在写C++程序时用的typedefine一样,在定义某种书写的格式。也像C++里面的类,而template的实例为数据对象

我们来看一下这些模板的通用格式:

 

template <template-name>{  //模板名称      <UUID>               //通用唯一标志,用来标志一个模板                           <member 1>,          //成员变量1      ………                                      <member n>,          //成员变量n      [restrictions]       //模板约束}


注释已经非常清晰了,其中<template-name>指定模板的名称,这个名称可以包含下画线(“_”),但不能以数字开头。<UUID>表示一个通用唯一标志(Universally Unique Identifier,我们在讲DirectInput的时候也提到过),用来标志一个模板,常用的格式分别为(8-4-4-16)和(8-4-4-4-12)两种,并且在X文件中用尖括号对(“_”)表示。比如:

 

<10dd46a9-775b-11cf-8f52-0040333594a3>

 

然后接下来的就是成员变量了,个数不限,比如像这样,AnimationOptions(动画选项)模板中定义了openclosed和positionquality这两个成员变量:

 

template AnimationOptions { <e2bf56c0-840f-11cf-8f52-0040333594a3> DWORD openclosed; DWORD positionquality;}

对于可取的成员变量的数据类型,浅墨也为大家整理出来了,可以在下表中取:

 

                     

可取的数据类型

精析

WORD

字类型,用16位表示

DWORD

双字类型,用32位表示

FLOAT

浮点类型

DOUBLE

64位双精度浮点型

CHAR

8位有符号字符类型

UCHAR

8位无符号字符类型

BYTE

8位无符号字符类型

STRING

包含结束符的字符串(char[])

CSTRING

带格式的C字符串

array

指定类型的数组


这里的array大家理解起来也许会出现偏差,我们提一下。

array数据类型用于定义一个任何有效的数据类型可以表示的数组类型,并且可以指定数组的维度(数组默认维度为1)。X文件中数组的基本语法的定义是这样的:

array <data-type><name>[<dimension-size>];

然后一个定义数组的实例:

template FloatKeys { <10dd46a9-775b-11cf-8f52-0040333594a3> DWORD nValues; array FLOAT values[nValues];}


最后我们看一下看起来有些神秘的所谓的[restrictions],模板约束。

[restrictions] 表示模板约束,用于指定在模板中可以定义的其他成员变量等。而根据模板约束的不同形式,可以将模板分为以下三大类:



1.开放式模板


顾名思义,开放式模板是指出了模板本身定义的成员变量以外,还可以向模板中添加其他的成员变量来达到定制模板的目的,在模板中通过方括号对("[ ]")表示。比如:

 

template Material { <3d82ab4d-62da-11cf-ab39-0020af71e433> ColorRGBA faceColor; FLOAT power; ColorRGB specularColor; ColorRGB emissiveColor; [...]    //喏,开放式模板的标识小尾巴}


2.约束式模板


约束式模板是指除了模板中定义的成员变量以外,只能够向模板中添加有限的几种数据类型的数据成员,而这些指定可以添加的(俗话说拿了人家offer的)数据类型我们在模板中列举出来。比如这样:

 

template FileSystem { <f6f23f43-7686-11cf-8f52-0040333594a3> STRING name; [Directory<UUID>,File<UUID>]   //喏,约束式模板的标识小尾巴}


3.封闭式模板

 

封闭式模板就比较没有创意了,在它出生的时候就注定了是那幅模样,不能向其中添加其他类型的数据成员。封闭式模板通常表示固定的数据结构,比如向量、矩阵,颜色等等。依然是一个例子:

 

template Coords2d { <f6f23f44-7686-11cf-8f52-0040333594a3> FLOAT u; FLOAT v;}  //封闭式模板,直接把小尾巴"[ ]"拿掉就行了。


相信不少朋友会把上面的Coords2d一眼看成Cocos2d - -,这里的Coords2d是定义纹理坐标向量的模板名称,不是那个众所周知,炙手可热的2D游戏引擎:)

 

 

4.常用的模板名称

 

接着我们看一下约定俗成的常用的模板的类型名,像一个小字典一样,X文件中的那些模板名基本上都整理在下面了:

 

AnimationSet 动画的组合,包括一个或者多个Animation。

 

Animation 描述一个动画,包含一个或几个AnimationKey

 

AnimationKey 动画关键帧,定义具体的动作数据,包括一些列旋转、移动、放缩、矩阵变换。

 

ColorRGB 定义RGB对象,包括三个Float的值,分别是R、G、B。

 

ColorRGBA 定义RGBA对象。包括四个Float的值,分别是R、G、B、alpha。

 

Coords2d 定义纹理坐标向量,包括两个Float值,分别是u、v。

 

FloatKeys 定义浮点数组,用来定义动画键数值,包括两个部分:浮点值个数,浮点值列表。

 

Material 定义材质信息,可以被应用到一个完整的Mesh对象,也可以应用到其中的一个面。包含:

           1.FaceColor环境光

           2.Power镜面反射的强度

           3.specularcolor镜面反射等等。

 

Matrix4X4 定义4X4矩阵,16个浮点数值。

 

Mesh 定义个Mesh对象,共有9个部分组成:

1、包含的顶点数

2、顶点列表,一个顶点包含三个浮点值

3、面数

4、面的顶点索引列表,每个面包含三个顶点

5、MeshFaceWraps 结构,暂时无用

6、MeshTextureCoords纹理坐标,可选

7、MeshNormals 法向,可选

8、MeshVertexColors 顶点颜色,默认为白色

9、MeshMaterialList 材质,不提供的话默认为白色。

 

MeshFace 面索引,包含两部分:面数,定点索引构成的面数组。

 

MeshTextureCoords 定义纹理坐标,包括:纹理坐标的个数,纹理坐标(每个纹理坐标有两个浮点值)。

 

MeshMaterialList 定义材质的应用,包括:多少个材质被使用,材质影响面的个数,面索引。

 

MeshNormals 定义Mesh的法向量,包括4部分:

1.nNormals法向量的个数=顶点数

2.Normals顶点法向量列表

3.nFaceNormals面的个数

4.FaceNormals面对应的法向量。

 

MeshVertexColors  指定顶点的颜色代替原来的材质,包含:顶点数目,颜色索引

 

TextureFilename  纹理的名称,字符串类型。

 

VertexDuplicationIndices  保留副本,用于精简Mesh的操作,包含:顶点数,原始顶点数,实际顶点数。

 

XSkinMeshHeader  描述被导出的SkinMesh相关信息,影响一个顶点的最多变换数目,影响每个面三个顶点的最大变换数目,影响一个顶点的骨骼数。

 

TimedFloatKeys 时间值,用于Animaterkey中定义时间间隔。

 

Vector 三维向量,三个浮点值。

 

SkinWeights 定义骨骼影响权重。包括以下几个部分:骨骼的名字,有多少个权重值,顶点的索引列表等等



3.实例化部分


首先告诉大家的是,首部部分一般是1行代码就搞定,模板部分一般一百来行代码搞定,而实例化部分一般几万行代码才搞得定。比如,我们这次选的“陆雪琪”的X文件就有63521行代码,以如下VS2010中的截图为证:


 

 

当然,这些代码不是我们去写的,而是在三维建模软件如3DS Max和Maya中用可视化的建模环境做出来之后,再导出为X文件格式的。

实例化部分其实就是把第二步中定义的那些模板进行实例化,填充数字,给他们具体的含义。可以这样理解,模板定义部分就是在“定义类”,而实例化部分就是在“实例化类”。

为了大家理解更加深刻,我们贴出“陆雪琪”的X文件的148~190行的代码,大家可以配合前面贴出的0~145行代码一起看:

 

Material Material__26 { 1.000000;1.000000;1.000000;1.000000;; 3.200000; 0.000000;0.000000;0.000000;; 0.000000;0.000000;0.000000;;  TextureFilename { "bd378f0.bmp"; }} Material Material__55_Material__29Sub0 { 1.000000;1.000000;1.000000;1.000000;; 3.200000; 0.000000;0.000000;0.000000;; 0.000000;0.000000;0.000000;;  TextureFilename { "9496a70.bmp"; }} Material Material__55_QQSub1 { 1.000000;1.000000;1.000000;1.000000;; 3.200000; 0.000000;0.000000;0.000000;; 0.000000;0.000000;0.000000;;  TextureFilename { "9622210.bmp"; }} Material Material__55_Material__30Sub2 { 1.000000;1.000000;1.000000;1.000000;; 3.200000; 0.000000;0.000000;0.000000;; 0.000000;0.000000;0.000000;;  TextureFilename { "353bd50.bmp"; }}


可以发现,就是在根据前面定义0~145行定义的那些模板,做填空题罢了。

 

 

 

三、详细注释的示例程序源代码

 

因为骨骼动画内容的特殊性,周边知识太多了,今天只能讲一小部分。为了满足大家的好奇心,我们这次先放出骨骼动画的第一个示例程序。里面有些知识还没讲到,大家可以回去自己钻研,或者是接着追浅墨后续文章的更新。而这篇文章的示例程序所含文件如下:

 

 

先给大家透露一下,其实在微软官方的DirectX SDK Samples中已经为我们把骨骼动画类封装好了,在一个名为SkinnedMesh的示例程序中。既然我们关于DirectX的知识都是拜SDK中的文档所赐,我们不妨学一学鲁迅先生教我们的“拿来主义”,直接把微软给我们写的那个骨骼动画类拿来用。

如果你的SDK是安装在D盘,那么这个骨骼动画的微软官方示例程序的路径就是如下:

D:\Program Files\Microsoft DirectX SDK(June 2010)\Samples\C++\Direct3D\SkinnedMesh

微软对Samples中代码的书写方式比较密集,一般实现代码都放在一个cpp文件中,比如这里的SkinnedMesh示例程序,基本上代码都挤在了一个名为skinnedmesh.cpp的1970行代码的源文件中,在这个源文件中有一个叫CAllocateHierarchy的类,还有几个好用的全局函数,我们直接拿过来用就好了,浅墨为大家整合在了CAllocateHierarchy.h和CAllocateHierarchy.cpp这两个文件中了。

 

另外,本次使用的”陆雪琪“X文件中对骨骼动画的存放,只存放了一个名为”剑舞“的动画,也就是说用这个X模型中就只有这一个动画。这个动画是浅墨在3DS Max中自己导出的,具体导入方式我们下次再讲(其实就是在panda插件中选项调一下)。在”陆雪琪“X文件的31895行,我们就可以找到这个用AnimationSet 来定义的sworddance(剑舞)的动画集出处,开头部分如下:

 

 

因为篇幅原因,更多内容咱们就下次再细讲了,这里贴出详细注释的main.cpp的代码和微软官方Samples中为我们写好的代码CAllocateHierarchy.h,其他的代码大家可以下源代码回去自己琢磨。


好吧,上代码,首先是CAllocateHierarchy.h:

#pragma once//=======================&
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值