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

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

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

作者:毛星云(浅墨)微博:@浅墨_毛星云 邮箱:[email protected]


这是答应大家的讲解骨骼动画的文章的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 0032
template ColorRGBA {
 <35ff44e0-6c7c-11cf-8f52-0040333594a3>
 FLOAT red;
 FLOAT green;
 FLOAT blue;
 FLOAT alpha;
}
 
template ColorRGB {
 
 FLOAT red;
 FLOAT green;
 FLOAT blue;
}
 
template Material {
 <3d82ab4d-62da-11cf-ab39-0020af71e433>
 ColorRGBA faceColor;
 FLOAT power;
 ColorRGB specularColor;
 ColorRGB emissiveColor;
 [...]
}
 
template TextureFilename {
 
 STRING filename;
}
 
template Frame {
 <3d82ab46-62da-11cf-ab39-0020af71e433>
 [...]
}
 
template Matrix4x4 {
 
 array FLOAT matrix[16];
}
 
template FrameTransformMatrix {
 
 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 {
 
 DWORD nNormals;
 array Vector normals[nNormals];
 DWORD nFaceNormals;
 array MeshFace faceNormals[nFaceNormals];
}
 
template MeshMaterialList {
 
 DWORD nMaterials;
 DWORD nFaceIndexes;
 array DWORD faceIndexes[nFaceIndexes];
 [Material<3d82ab4d-62da-11cf-ab39-0020af71e433>]
}
 
template Coords2d {
 
 FLOAT u;
 FLOAT v;
}
 
template MeshTextureCoords {
 
 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 {
 
 DWORD openclosed;
 DWORD positionquality;
}
 
template FloatKeys {
 <10dd46a9-775b-11cf-8f52-0040333594a3>
 DWORD nValues;
 array FLOAT values[nValues];
}
 
template TimedFloatKeys {
 
 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 {  //模板名称
                     //通用唯一标志,用来标志一个模板                     
      ,          //成员变量1
      ………                                
      ,          //成员变量n
      [restrictions]       //模板约束
}


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

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

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

template AnimationOptions {
 
 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 [];

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

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 {
 
 STRING name;
 [Directory,File]   //喏,约束式模板的标识小尾巴
}


3.封闭式模板

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

template Coords2d {
 
 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

//=============================================================================
// Desc: CAllocateHierarchy.h
// 来自微软官方DirectX SDK Samples中的骨骼动画类
//=============================================================================

#include 
#include 
#include "D3DUtil.h"


//-----------------------------------------------------------------------------
// Name: struct D3DXFRAME_DERIVED
// Desc: 继承自DXDXFRAME结构的结构
//-----------------------------------------------------------------------------
struct D3DXFRAME_DERIVED: public D3DXFRAME
{
	D3DXMATRIXA16 CombinedTransformationMatrix;
};


//-----------------------------------------------------------------------------
// Name: struct D3DXMESHCONTAINER_DERIVED
// Desc: 继承自D3DXMESHCONTAINER结构的结构
//-----------------------------------------------------------------------------
struct D3DXMESHCONTAINER_DERIVED: public D3DXMESHCONTAINER
{
	LPDIRECT3DTEXTURE9*  ppTextures;            //纹理数组
	LPD3DXMESH           pOrigMesh;             //原始网格
	LPD3DXATTRIBUTERANGE pAttributeTable;
	DWORD                NumAttributeGroups;    //属性组数量,即子网格数量
	DWORD                NumInfl;               //每个顶点最多受多少骨骼的影响
	LPD3DXBUFFER         pBoneCombinationBuf;   //骨骼结合表
	D3DXMATRIX**         ppBoneMatrixPtrs;      //存放骨骼的组合变换矩阵
	D3DXMATRIX*          pBoneOffsetMatrices;   //存放骨骼的初始变换矩阵
	DWORD                NumPaletteEntries;     //骨骼数量上限
	bool                 UseSoftwareVP;         //标识是否使用软件顶点处理
};


//-----------------------------------------------------------------------------
// Name: class CAllocateHierarchy
// Desc: 来自微软官方DirectX SDK Samples中的骨骼动画类,这个类用来从.X文件加载框架层次和网格模型数据
// 核心点:      #define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method 
//-----------------------------------------------------------------------------
class CAllocateHierarchy: public ID3DXAllocateHierarchy
{
public:
	STDMETHOD(CreateFrame)(THIS_ LPCSTR Name, LPD3DXFRAME *ppNewFrame);
	STDMETHOD(CreateMeshContainer)( THIS_ LPCSTR              Name, 
		CONST D3DXMESHDATA*       pMeshData,
		CONST D3DXMATERIAL*       pMaterials, 
		CONST D3DXEFFECTINSTANCE* pEffectInstances, 
		DWORD                     NumMaterials, 
		CONST DWORD *             pAdjacency, 
		LPD3DXSKININFO pSkinInfo, 
		LPD3DXMESHCONTAINER *ppNewMeshContainer);    
	STDMETHOD(DestroyFrame)(THIS_ LPD3DXFRAME pFrameToFree);
	STDMETHOD(DestroyMeshContainer)(THIS_ LPD3DXMESHCONTAINER pMeshContainerBase);
};




//-----------------------------------------------------------------------------
// Desc: 来自微软官方DirectX SDK Samples中的骨骼动画全局函数
//-----------------------------------------------------------------------------
void DrawFrame( IDirect3DDevice9* pd3dDevice, LPD3DXFRAME pFrame );
void DrawMeshContainer( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase, LPD3DXFRAME pFrameBase );
HRESULT SetupBoneMatrixPointers( LPD3DXFRAME pFrameBase, LPD3DXFRAME pFrameRoot );
void UpdateFrameMatrices( LPD3DXFRAME pFrameBase, LPD3DXMATRIX pParentMatrix );


main.cpp的注释代码风格浅墨改了一下,自己觉得工整多了,希望大家会喜欢:)

//-----------------------------------【程序说明】----------------------------------------------
// 【Visual C++】游戏开发系列配套源码五十二  浅墨DirectX教程二十 骨骼动画来袭(一)
// VS2010版
// 2013年4月 Create by 浅墨
// 背景音乐素材出处: 最终幻想   Eternal Love (Short Version)
// 人物模型素材出处:《诛仙》   陆雪琪
//------------------------------------------------------------------------------------------------


//-----------------------------------【宏定义部分】--------------------------------------------
// 描述:定义一些辅助宏
//------------------------------------------------------------------------------------------------
#define WINDOW_WIDTH	932						//为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define WINDOW_HEIGHT 700							//为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE	 _T("【致我们永不熄灭的游戏开发梦想】浅墨DirectX教程二十 骨骼动画来袭(一)博文配套示例程序 by浅墨") //为窗口标题定义的宏


//-----------------------------------【头文件包含部分】---------------------------------------
//	描述:包含程序所依赖的头文件
//------------------------------------------------------------------------------------------------                                                                                      
#include 
#include 
#include 
#include  
#include "DirectInputClass.h"
#include "CameraClass.h"
#include "SkyBoxClass.h"
#include "SnowParticleClass.h"
#include "AllocateHierarchyClass.h"


//-----------------------------------【库文件包含部分】---------------------------------------
//	描述:包含程序所依赖的库文件
//------------------------------------------------------------------------------------------------  
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib, "dinput8.lib")     // 使用DirectInput必须包含的库文件,注意这里有8
#pragma comment(lib,"dxguid.lib")
#pragma comment(lib, "winmm.lib") 


// 地板的顶点结构
struct CUSTOMVERTEX
{
	FLOAT _x, _y, _z;
	FLOAT _u, _v ;
	CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT u, FLOAT v)
		: _x(x), _y(y), _z(z), _u(u), _v(v) {}
};
#define D3DFVF_CUSTOMVERTEX  (D3DFVF_XYZ | D3DFVF_TEX1)





//-----------------------------------【全局变量声明部分】-------------------------------------
//	描述:全局变量的声明
//------------------------------------------------------------------------------------------------
LPDIRECT3DDEVICE9					g_pd3dDevice = NULL;				//Direct3D设备对象
LPD3DXFONT								g_pTextFPS =NULL;    //字体COM接口
LPD3DXFONT								g_pTextAdaperName = NULL;  // 显卡信息的2D文本
LPD3DXFONT								g_pTextHelper = NULL;  // 帮助信息的2D文本
LPD3DXFONT								g_pTextInfor= NULL;  // 绘制信息的2D文本
float											g_FPS= 0.0f;       //一个浮点型的变量,代表帧速率
wchar_t										g_strFPS[50] ={0};    //包含帧速率的字符数组
wchar_t										g_strAdapterName[60] ={0};   //包含显卡名称的字符数组
D3DXMATRIX							g_matWorld;							//世界矩阵
D3DLIGHT9								g_Light;   //全局光照
DInputClass*								g_pDInput = NULL;				//DInputClass类的指针实例
CameraClass*							g_pCamera = NULL;				//摄像机类的指针实例
SkyBoxClass*								g_pSkyBox=NULL;					//天空盒类的指针实例
SnowParticleClass*						g_pSnowParticles = NULL;		//雪花粒子系统的指针实例

//四个和骨骼动画相关的全局变量
LPD3DXFRAME                 g_pFrameRoot      = NULL;
D3DXMATRIX*                 g_pBoneMatrices   = NULL;
CAllocateHierarchy*         g_pAllocateHier   = NULL;
LPD3DXANIMATIONCONTROLLER   g_pAnimController = NULL;

LPDIRECT3DVERTEXBUFFER9		g_pFloorVBuffer      = NULL;  //地板顶点缓存对象
LPDIRECT3DTEXTURE9				g_pFloorTexture   = NULL;  //地板纹理对象

 
//-----------------------------------【全局函数声明部分】-------------------------------------
//	描述:全局函数声明,防止“未声明的标识”系列错误
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK		WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
HRESULT						Direct3D_Init(HWND hwnd,HINSTANCE hInstance);
HRESULT						Objects_Init();
void								Direct3D_Render( HWND hwnd,FLOAT fTimeDelta);
void								Direct3D_Update( HWND hwnd,FLOAT fTimeDelta);
void								Direct3D_CleanUp( );
float								Get_FPS();
void								HelpText_Render(HWND hwnd);


//-----------------------------------【WinMain( )函数】--------------------------------------
//	描述:Windows应用程序的入口函数,我们的程序从这里开始
//------------------------------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{

	//开始设计一个完整的窗口类
	WNDCLASSEX wndClass={0} ;				//用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX,用于之后窗口的各项初始化    
	wndClass.cbSize = sizeof( WNDCLASSEX ) ;	//设置结构体的字节数大小
	wndClass.style = CS_HREDRAW | CS_VREDRAW;	//设置窗口的样式
	wndClass.lpfnWndProc = WndProc;				//设置指向窗口过程函数的指针
	wndClass.cbClsExtra		= 0;
	wndClass.cbWndExtra		= 0;
	wndClass.hInstance = hInstance;				//指定包含窗口过程的程序的实例句柄。
	wndClass.hIcon=(HICON)::LoadImage(NULL,_T("GameMedia\\icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //从全局的::LoadImage函数从本地加载自定义ico图标
	wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );    //指定窗口类的光标句柄。
	wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);  //为hbrBackground成员指定一个灰色画刷句柄
	wndClass.lpszMenuName = NULL;						//用一个以空终止的字符串,指定菜单资源的名字。
	wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");		//用一个以空终止的字符串,指定窗口类的名字。

	if( !RegisterClassEx( &wndClass ) )				//设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
		return -1;		

	HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,			//喜闻乐见的创建窗口函数CreateWindow
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
		WINDOW_HEIGHT, NULL, NULL, hInstance, NULL );


	//Direct3D资源的初始化,调用失败用messagebox予以显示
	if (!(S_OK==Direct3D_Init (hwnd,hInstance)))
	{
		MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0); //使用MessageBox函数,创建一个消息窗口 
	}
	PlaySound(L"GameMedia\\Eternal Love (Short Version).wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP);   //循环播放背景音乐

	

	MoveWindow(hwnd,200,10,WINDOW_WIDTH,WINDOW_HEIGHT,true);   //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,10)处
	ShowWindow( hwnd, nShowCmd );    //调用Win32函数ShowWindow来显示窗口
	UpdateWindow(hwnd);  //对窗口进行更新,就像我们买了新房子要装修一样

	//进行DirectInput类的初始化
	g_pDInput = new DInputClass();
	g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

	//消息循环过程
	MSG msg = { 0 };  //初始化msg
	while( msg.message != WM_QUIT )			//使用while循环
	{
		static FLOAT fLastTime  = (float)::timeGetTime();
		static FLOAT fCurrTime  = (float)::timeGetTime();
		static FLOAT fTimeDelta = 0.0f;
		fCurrTime  = (float)::timeGetTime();
		fTimeDelta = (fCurrTime - fLastTime) / 1000.0f;
		fLastTime  = fCurrTime;

		if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )   //查看应用程序消息队列,有消息时将队列中的消息派发出去。
		{
			TranslateMessage( &msg );		//将虚拟键消息转换为字符消息
			DispatchMessage( &msg );		//该函数分发一个消息给窗口程序。
		}
		else
		{
			Direct3D_Update(hwnd,fTimeDelta);         //调用更新函数,进行画面的更新
			Direct3D_Render(hwnd,fTimeDelta);			//调用渲染函数,进行画面的渲染			
		}
	}

	UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance);
	return 0;  
}


//-----------------------------------【WndProc( )函数】--------------------------------------
//	描述:窗口过程函数WndProc,对窗口消息进行处理
//------------------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )   //窗口过程函数WndProc
{
	switch( message )				//switch语句开始
	{
	case WM_PAINT:					 // 客户区重绘消息
		Direct3D_Render(hwnd,0.0f);          //调用Direct3D_Render函数,进行画面的绘制
		ValidateRect(hwnd, NULL);   // 更新客户区的显示
		break;									//跳出该switch语句

	case WM_KEYDOWN:                // 键盘按下消息
		if (wParam == VK_ESCAPE)    // ESC键
			DestroyWindow(hwnd);    // 销毁窗口, 并发送一条WM_DESTROY消息
		break;
	case WM_DESTROY:				//窗口销毁消息
		Direct3D_CleanUp();     //调用Direct3D_CleanUp函数,清理COM接口对象
		PostQuitMessage( 0 );		//向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
		break;						//跳出该switch语句

	default:						//若上述case条件都不符合,则执行该default语句
		return DefWindowProc( hwnd, message, wParam, lParam );		//调用缺省的窗口过程来为应用程序没有处理的窗口消息提供缺省的处理。
	}

	return 0;					//正常退出
}


//-----------------------------------【Direct3D_Init( )函数】----------------------------------
//	描述:Direct3D初始化函数,进行Direct3D的初始化
//------------------------------------------------------------------------------------------------
HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance)
{

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象
	//--------------------------------------------------------------------------------------
	LPDIRECT3D9  pD3D = NULL; //Direct3D接口对象的创建
	if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商
 			return E_FAIL;

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息
	//--------------------------------------------------------------------------------------
	D3DCAPS9 caps; int vp = 0;
	if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) )
		{
			return E_FAIL;
		}
	if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;   //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的
	else
		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体
	//--------------------------------------------------------------------------------------
	D3DPRESENT_PARAMETERS d3dpp; 
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	d3dpp.BackBufferWidth            = WINDOW_WIDTH;
	d3dpp.BackBufferHeight           = WINDOW_HEIGHT;
	d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8;
	d3dpp.BackBufferCount            = 2;
	d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;
	d3dpp.MultiSampleQuality         = 0;
	d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD; 
	d3dpp.hDeviceWindow              = hwnd;
	d3dpp.Windowed                   = true;
	d3dpp.EnableAutoDepthStencil     = true; 
	d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8;
	d3dpp.Flags                      = 0;
	d3dpp.FullScreen_RefreshRateInHz = 0;
	d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口
	//--------------------------------------------------------------------------------------
	if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, 
		hwnd, vp, &d3dpp, &g_pd3dDevice)))
		return E_FAIL;


	//获取显卡信息到g_strAdapterName中,并在显卡名称之前加上“当前显卡型号:”字符串
	 wchar_t TempName[60]=L"当前显卡型号:";   //定义一个临时字符串,且方便了把"当前显卡型号:"字符串引入我们的目的字符串中
	 D3DADAPTER_IDENTIFIER9 Adapter;  //定义一个D3DADAPTER_IDENTIFIER9结构体,用于存储显卡信息
	 pD3D->GetAdapterIdentifier(0,0,&Adapter);//调用GetAdapterIdentifier,获取显卡信息
	 int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);//显卡名称现在已经在Adapter.Description中了,但是其为char类型,我们要将其转为wchar_t类型
	 MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);//这步操作完成后,g_strAdapterName中就为当前我们的显卡类型名的wchar_t型字符串了
	 wcscat_s(TempName,g_strAdapterName);//把当前我们的显卡名加到“当前显卡型号:”字符串后面,结果存在TempName中
	 wcscpy_s(g_strAdapterName,TempName);//把TempName中的结果拷贝到全局变量g_strAdapterName中,大功告成~

	if(!(S_OK==Objects_Init())) return E_FAIL;

	SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉

	return S_OK;
}


//-----------------------------------【Object_Init( )函数】--------------------------------------
//	描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化
//--------------------------------------------------------------------------------------------------
HRESULT Objects_Init()
{
	//创建字体
	D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("Calibri"), &g_pTextFPS);
	D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"华文中宋", &g_pTextAdaperName); 
	D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微软雅黑", &g_pTextHelper); 
	D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑体", &g_pTextInfor); 


	// 创建地面顶点缓存
	g_pd3dDevice->CreateVertexBuffer(4 * sizeof(CUSTOMVERTEX), 0, 
		D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &g_pFloorVBuffer, NULL);

	CUSTOMVERTEX *pVertices = NULL;
	g_pFloorVBuffer->Lock(0, 0, (void**)&pVertices, 0);
	pVertices[0] = CUSTOMVERTEX(-5000.0f, 0.0f, -5000.0f,  0.0f, 30.0f);
	pVertices[1] = CUSTOMVERTEX(-5000.0f, 0.0f,  5000.0f,  0.0f,  0.0f);
	pVertices[2] = CUSTOMVERTEX( 5000.0f, 0.0f, -5000.0f, 30.0f, 30.0f); 
	pVertices[3] = CUSTOMVERTEX( 5000.0f, 0.0f,  5000.0f, 30.0f,  0.0f);
	g_pFloorVBuffer->Unlock();


	//创建地面纹理
	D3DXCreateTextureFromFile(g_pd3dDevice, L"GameMedia\\wood.jpg", &g_pFloorTexture);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);


	// 设置光照  
	::ZeroMemory(&g_Light, sizeof(g_Light));  
	g_Light.Type          = D3DLIGHT_DIRECTIONAL;  
	g_Light.Ambient       = D3DXCOLOR(0.7f, 0.7f, 0.7f, 1.0f);  
	g_Light.Diffuse       = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);  
	g_Light.Specular      = D3DXCOLOR(0.9f, 0.9f, 0.9f, 1.0f);  
	g_Light.Direction     = D3DXVECTOR3(1.0f, 1.0f, 1.0f);  
	g_pd3dDevice->SetLight(0, &g_Light);  
	g_pd3dDevice->LightEnable(0, true);  
	g_pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true);  
	g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, true);



	// 创建并初始化虚拟摄像机
	g_pCamera = new CameraClass(g_pd3dDevice);
	g_pCamera->SetCameraPosition(&D3DXVECTOR3(0.0f, 300.0f, -800.0f));  //设置摄像机所在的位置
	g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 400.0f, 0.0f));  //设置目标观察点所在的位置
	g_pCamera->SetViewMatrix();  //设置取景变换矩阵
	D3DXMATRIX matProj;
	D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f, 1.0f, 1.0f, 200000.0f);
	g_pCamera->SetProjMatrix(&matProj);

	//创建并初始化天空盒
	g_pSkyBox = new SkyBoxClass( g_pd3dDevice );
	g_pSkyBox->LoadSkyTextureFromFile(
									L"GameMedia\\frontaw2.jpg",
									L"GameMedia\\backaw2.jpg",
									L"GameMedia\\leftaw2.jpg",
									L"GameMedia\\rightaw2.jpg", 
									L"GameMedia\\topaw2.jpg");//从文件加载前、后、左、右、顶面5个面的纹理图
	g_pSkyBox->InitSkyBox(50000);  //设置天空盒的边长

	//创建并初始化雪花粒子系统  
	g_pSnowParticles = new SnowParticleClass(g_pd3dDevice);  
	g_pSnowParticles->InitSnowParticle();  

	// 创建骨骼动画
	g_pAllocateHier = new CAllocateHierarchy();
	D3DXLoadMeshHierarchyFromX(L"lxq.x", D3DXMESH_MANAGED, g_pd3dDevice, 
	g_pAllocateHier, NULL, &g_pFrameRoot, &g_pAnimController);
	SetupBoneMatrixPointers(g_pFrameRoot, g_pFrameRoot);

	//因为这个X文件中仅有一个默认的舞剑动作,所以以下代码可用可不用
// 	LPD3DXANIMATIONSET pAnimationSet = NULL;
// 	g_pAnimController->GetAnimationSetByName("sworddance", &pAnimationSet);
// 	g_pAnimController->SetTrackAnimationSet((UINT)1.0, pAnimationSet);


	return S_OK;
}


//-----------------------------------【Direct3D_Update( )函数】--------------------------------
//	描述:不是即时渲染代码但是需要即时调用的,如按键后的坐标的更改,都放在这里
//--------------------------------------------------------------------------------------------------
void	Direct3D_Update( HWND hwnd,FLOAT fTimeDelta)
{
	//使用DirectInput类读取数据  
	g_pDInput->GetInput();  

	// 沿摄像机各分量移动视角  
	if (g_pDInput->IsKeyDown(DIK_A))  g_pCamera->MoveAlongRightVec(-1.0f);  
	if (g_pDInput->IsKeyDown(DIK_D))  g_pCamera->MoveAlongRightVec( 1.0f);  
	if (g_pDInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 1.0f);  
	if (g_pDInput->IsKeyDown(DIK_S))  g_pCamera->MoveAlongLookVec(-1.0f);  
	if (g_pDInput->IsKeyDown(DIK_R))  g_pCamera->MoveAlongUpVec( 1.0f);  
	if (g_pDInput->IsKeyDown(DIK_F))  g_pCamera->MoveAlongUpVec(-1.0f);  

	//沿摄像机各分量旋转视角  
	if (g_pDInput->IsKeyDown(DIK_LEFT))  g_pCamera->RotationUpVec(-0.003f);  
	if (g_pDInput->IsKeyDown(DIK_RIGHT))  g_pCamera->RotationUpVec( 0.003f);  
	if (g_pDInput->IsKeyDown(DIK_UP))  g_pCamera->RotationRightVec(-0.003f);  
	if (g_pDInput->IsKeyDown(DIK_DOWN))  g_pCamera->RotationRightVec( 0.003f);  
	if (g_pDInput->IsKeyDown(DIK_Q)) g_pCamera->RotationLookVec(0.001f);  
	if (g_pDInput->IsKeyDown(DIK_E)) g_pCamera->RotationLookVec( -0.001f);  

	//鼠标控制右向量和上向量的旋转  
	g_pCamera->RotationUpVec(g_pDInput->MouseDX()* 0.0003f);  
	g_pCamera->RotationRightVec(g_pDInput->MouseDY() * 0.0003f);  

	//鼠标滚轮控制观察点收缩操作  
	static FLOAT fPosZ=0.0f;  
	fPosZ += g_pDInput->MouseDZ()*0.03f;  

	//计算并设置取景变换矩阵  
	D3DXMATRIX matView;  
	g_pCamera->CalculateViewMatrix(&matView);  
	g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);  

	//把正确的世界变换矩阵存到g_matWorld中  
	D3DXMatrixTranslation(&g_matWorld, 0.0f, 0.0f, fPosZ);  


	//以下这段代码用于限制鼠标光标移动区域
	POINT lt,rb;
	RECT rect;
	GetClientRect(hwnd,&rect);  //取得窗口内部矩形
	//将矩形左上点坐标存入lt中
	lt.x = rect.left;
	lt.y = rect.top;
	//将矩形右下坐标存入rb中
	rb.x = rect.right;
	rb.y = rect.bottom;
	//将lt和rb的窗口坐标转换为屏幕坐标
	ClientToScreen(hwnd,<);
	ClientToScreen(hwnd,&rb);
	//以屏幕坐标重新设定矩形区域
	rect.left = lt.x;
	rect.top = lt.y;
	rect.right = rb.x;
	rect.bottom = rb.y;
	//限制鼠标光标移动区域
	ClipCursor(&rect);

	ShowCursor(false);		//隐藏鼠标光标


	// 设置骨骼动画的矩阵
	D3DXMATRIX matFinal , matScal;
	D3DXMatrixIdentity(&matFinal);
	D3DXMatrixScaling(&matScal, 5.0f, 9.0f, 5.0f);
	matFinal = matScal *matFinal;
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &matFinal);

	// 更新骨骼动画
	g_pAnimController->AdvanceTime(fTimeDelta, NULL);  //设置骨骼动画的时间
	UpdateFrameMatrices(g_pFrameRoot, &matFinal);   //更新框架中的变换矩阵


}



//-----------------------------------【Direct3D_Render( )函数】-------------------------------
//	描述:使用Direct3D进行渲染
//--------------------------------------------------------------------------------------------------
void Direct3D_Render(HWND hwnd,FLOAT fTimeDelta)
{
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之一】:清屏操作
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100, 255, 255), 1.0f, 0);

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之二】:开始绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->BeginScene();                     // 开始绘制
	
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之三】:正式绘制
	//--------------------------------------------------------------------------------------


	//-----------------------------【绘制骨骼动画】------------------------
	DrawFrame(g_pd3dDevice, g_pFrameRoot);

	//-----------------------------【绘制地板】-----------------------------
	D3DXMATRIX matFloor;
	D3DXMatrixTranslation(&matFloor, 0.0f, 0.0f, 0.0f);
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &matFloor);
	g_pd3dDevice->SetStreamSource(0, g_pFloorVBuffer, 0, sizeof(CUSTOMVERTEX));
	g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
	g_pd3dDevice->SetTexture(0, g_pFloorTexture);
	g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

	//-----------------------------【绘制天空】-----------------------------
	D3DXMATRIX matSky,matTransSky,matRotSky;
	D3DXMatrixTranslation(&matTransSky,0.0f,-13000.0f,0.0f);
	D3DXMatrixRotationY(&matRotSky, -0.00002f*timeGetTime());   //旋转天空网格, 简单模拟云彩运动效果
	matSky=matTransSky*matRotSky;
	g_pSkyBox->RenderSkyBox(&matSky, false);

	//-----------------------------【绘制雪花粒子系统】------------------------
	g_pSnowParticles->UpdateSnowParticle(fTimeDelta);
	g_pSnowParticles->RenderSnowParticle();

	//-----------------------------【绘制文字信息】-----------------------------
	HelpText_Render(hwnd);


	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之四】:结束绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->EndScene();                       // 结束绘制
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之五】:显示翻转
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);  // 翻转与显示
	 
}


//-----------------------------------【HelpText_Render( )函数】-------------------------------
//	描述:封装了帮助信息的函数
//--------------------------------------------------------------------------------------------------
void HelpText_Render(HWND hwnd)
{
	//定义一个矩形,用于获取主窗口矩形
	RECT formatRect;
	GetClientRect(hwnd, &formatRect);

	//在窗口右上角处,显示每秒帧数
	formatRect.top = 5;
	int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() );
	g_pTextFPS->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA(0,239,136,255));

	//显示显卡类型名
	g_pTextAdaperName->DrawText(NULL,g_strAdapterName, -1, &formatRect, 
		DT_TOP | DT_LEFT, D3DXCOLOR(1.0f, 0.5f, 0.0f, 1.0f));

	// 输出帮助信息
	formatRect.left = 0,formatRect.top = 380;
	g_pTextInfor->DrawText(NULL, L"控制说明:", -1, &formatRect, 
		DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(235,123,230,255));
	formatRect.top += 35;
	g_pTextHelper->DrawText(NULL, L"    W:向前飞翔     S:向后飞翔 ", -1, &formatRect, 
		DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
	formatRect.top += 25;
	g_pTextHelper->DrawText(NULL, L"    A:向左飞翔     D:向右飞翔", -1, &formatRect, 
		DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
	formatRect.top += 25;
	g_pTextHelper->DrawText(NULL, L"    R:垂直向上飞翔     F:垂直向下飞翔", -1, &formatRect, 
		DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
	formatRect.top += 25;
	g_pTextHelper->DrawText(NULL, L"    Q:向左倾斜       E:向右倾斜", -1, &formatRect, 
		DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
	formatRect.top += 25;
	g_pTextHelper->DrawText(NULL, L"    上、下、左、右方向键、鼠标移动:视角变化 ", -1, &formatRect, 
		DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
	formatRect.top += 25;
	g_pTextHelper->DrawText(NULL, L"     鼠标滚轮:人物模型Y轴方向移动", -1, &formatRect, 
		DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
	formatRect.top += 25;
	g_pTextHelper->DrawText(NULL, L"    ESC键 : 退出程序", -1, &formatRect, 
		DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
}


//-----------------------------------【Get_FPS( )函数】------------------------------------------
//	描述:用于计算每秒帧速率的一个函数
//--------------------------------------------------------------------------------------------------
float Get_FPS()
{

	//定义四个静态变量
	static float  fps = 0; //我们需要计算的FPS值
	static int    frameCount = 0;//帧数
	static float  currentTime =0.0f;//当前时间
	static float  lastTime = 0.0f;//持续时间

	frameCount++;//每调用一次Get_FPS()函数,帧数自增1
	currentTime = timeGetTime()*0.001f;//获取系统时间,其中timeGetTime函数返回的是以毫秒为单位的系统时间,所以需要乘以0.001,得到单位为秒的时间

	//如果当前时间减去持续时间大于了1秒钟,就进行一次FPS的计算和持续时间的更新,并将帧数值清零
	if(currentTime - lastTime > 1.0f) //将时间控制在1秒钟
	{
		fps = (float)frameCount /(currentTime - lastTime);//计算这1秒钟的FPS值
		lastTime = currentTime; //将当前时间currentTime赋给持续时间lastTime,作为下一秒的基准时间
		frameCount    = 0;//将本次帧数frameCount值清零
	}

	return fps;
}



//-----------------------------------【Direct3D_CleanUp( )函数】--------------------------------
//	描述:对Direct3D的资源进行清理,释放COM接口对象
//---------------------------------------------------------------------------------------------------
void Direct3D_CleanUp()
{

	//释放COM接口对象
	SAFE_DELETE(g_pDInput);
	SAFE_RELEASE(g_pd3dDevice);
	SAFE_RELEASE(g_pTextAdaperName)
	SAFE_RELEASE(g_pTextHelper)
	SAFE_RELEASE(g_pTextInfor)
	SAFE_RELEASE(g_pTextFPS)
	SAFE_RELEASE(g_pd3dDevice)
}





背景音乐使用的是来自《最终幻想13》原声带的一曲《Eternal Love (Short Version)》,挺好听的

最后我们看一下运行截图:






当然,我们依然可以像之前的示例程序一样,通过鼠标和键盘来控制视角的移动,全方位进行观察:




文章最后,依旧是放出本篇文章配套源代码的下载:


本篇文章配套的程序源代码请点击这里下载:


【浅墨DirectX提高班】配套源代码之二十下载


等了10个多小时,重新改资源名字又传了好几遍。下面是我的心情。。。。尼玛,我必须吐槽下CSDN下载频道了,程序源码传了近20个小时了都不显示出来,闹哪样啊。。。。谁设计的后端架构啊,动不动就闹大姨妈,太不科学了。。。我靠,和12306有的一拼,怒了。。。。

2013年4月23日:过了30多个小时,总算是显示出来了。不过把我给大家写的资源描述给吞了,而且一显示就是3个。。。。CSDN下载频道有时候真蛋疼。。。

如果下个星期还是这样,浅墨就到新浪微博去@蒋老板。。。。)




以上就是本节笔记的全部内容,更多精彩内容,且听下回分解。

浅墨在这里,希望喜欢游戏开发系列文章的朋友们能留下你们的评论,每次浅墨登陆博客看到大家的留言的时候都会非常开心,感觉自己正在传递一种信仰,一种精神,感觉自己不是一个人在战斗

文章最后,依然是【每文一语】栏目,今天的句子是:


面对生活,我们没有选择,但是请始终相信,现在所经历的一切,都有它存在的意义


加油:)




下周一,让我们离游戏开发的梦想更近一步。

下周一,游戏开发笔记,我们,不见不散。


大家可以在这里找到浅墨:【浅墨的新浪微博】http://www.weibo.com/u/1723155442




你可能感兴趣的:(【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一))