【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观...

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

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

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



在本篇文章中,我们一起详细探索了Direct3D网格相关的知识,对当前两款主流的三维建模软件(3DS Max和Maya)进行了介绍,了解了如何从3DS Max 中导出X文件,以及如何从X文件加载三位模型到DirectX游戏程序中。文章最后,我们依旧配了一个比较好玩的demo来让大家对本篇文章所学的知识融会贯通,最后提供了这个demo详细注释的源代码下载。



首先我们复习一个之前讲过的概念。

在计算机所描绘的3D世界中,所有的物体模型(如树木,人物,山峦)都是通过多边形网格来逼近表示的,就像这幅DOTA中的英雄幻影刺客(PA)的对比图一样:


幻影刺客镇文~嗯,我们开讲~


一、网格模型技术的前生今世


网格模型是一种将物体模型的顶点数据、纹理、材质等信息存储在一个外部文件中的3D物体模型。对于那些简单的图元描述的图形,比如点,线,三角形等等,我们可以通过写代码指定顶点数据,索引数据,法线向量,纹理和材质等等信息。但对于复杂的3D物体的话,采用这种方式显然是不现实的。因此,Direct3D提供了一种称作网格模型的技术,可以从各种特定的文件格式中读取和绘制3D图形,极大地方便了游戏的开发。

使用网格模型最普遍的方式是从外部的3D模型文件中加载一个网格。而这些3D模型通常都是由3D建模软件生成的,比较复杂的网格数据。目前市面上主流的3D建模软件有3DS Max和Maya。而目前流行的3D模型文件格式有.3ds、.max、.obj以及.mb。其中.3ds、.max为3DS Max常用的格式,.mb为Maya常用的格式,而.obj为3DSMax和Maya通用的文件格式。


二、认识三维建模软件


再不厌其烦地说一遍,我们在通常的三维游戏开发中,常常要涉及到非常复杂的三维物体数据模型,如果用我们之前讲的知识,顶点缓存索引缓存,通过写代码来构造这些三维模型,显然是不合实际的。

这些复杂物体的模型通常需要利用专业的三维建模软件来制作。目前市面上主流的3D建模软件有3DS Max和Maya。这两款在三维建模行业作为竞争对手的软件,目前同为Autodesk公司所有,这倒是有些令人匪夷所思。下面我们先来分别介绍一下当前市场上主流的三维建模软件(当然,不仅仅是用于三维建模这么简单)3DS Max和Maya。

Ⅰ、3DS Max软件简介


3D Studio Max,常简称为3dsMax或MAX,是Autodesk公司开发的基于PC系统的三维动画渲染和制作软件。其前身是基于DOS操作系统的3D Studio系列软件,最新版本是2012。在Windows NT出现以前,工业级的CG制作被SGI图形工作站所垄断。3D Studio Max + Windows NT组合的出现一下子降低了CG制作的门槛,首选开始运用在电脑游戏中的动画制作,后更进一步开始参与影视片的特效制作,例如X战警II,最后的武士等。

3ds Max软件截图:



Ⅱ、Maya软件简介


Maya是美国Autodesk公司出品的世界顶级的三维动画软件,应用对象是专业的影视广告,角色动画,电影特技等。Maya功能完善,工作灵活,易学易用,制作效率极高,渲染真实感极强,是电影级别的高端制作软件。

Maya软件截图:


Ⅲ、Maya和3ds Max的区别与联系


MAYA和3d Max都是高端3D软件,两者之间都有很多相同的功能,像创建模型,渲染材质,动画制作等等.但就运用实际情况而言,Max更加适合于游戏,建筑学,室内设计等等,而MAYA可以说是专门为影视特效而生的一款软件,这也是当初Alias设计MAYA的意图.MAYA在如角色动画/运动学模拟/以及完美的材质系统,使得MAYA所创造出来的3维效果如此逼真,可以说全世界的电影特效工作室都必须用到MAYA

MAYA 的用户界面也比3dsmax要人性化点,Maya 是 Alias|Wavefront ( 2003 年 7月 更名为 Alias )公司的产品,作为三维动画软件的后起之秀,深受业界欢迎和钟爱。

MAYA软件应用主要是动画片制作、电影制作、电视栏目包装、电视广告、游戏动画制作等。3dsmax软件应用主要是动画片制作、游戏动画制作、建筑效果图、建筑动画等。 MAYA的基础层次更高,3dsmax属于普及型三维软件,有条件当然学MAYA。

Maya的CG功能十分全面,建模、粒子系统、毛发生成、植物创建、衣料仿真等等。可以说,当3dsmax用户匆忙地寻找第三方插件时,Maya用户已经可以早早地安心工作了。可以说,从建模到动画,到速度,Maya都非常出色。Maya主要是为了影视应用而研发的。

3d MAX和Maya都是功能强大的三维制作软件,各有很大优点,但是,不同的行业用不同的软件会让制作比较轻松,所以说,3d MAX和Maya究竟哪个好,哪个更好用与所从事的行业有着一定联系。

3d MAX在建筑动画效果上要强于Maya,而Maya在影视动画上又略胜于3d max,两款软件没有太大的本质性区别,只是看个人掌握哪个软件比较顺手罢了。

目前,业内已开始对软件使用做了一些细分。3d max主要用来做建筑,国内多数公司喜欢拿他做游戏,附带着可以做影视动画,建模比较方便。Maya主要用来做影视动画,在动画和动力学模块上很强大,国内不少游戏公司慢慢将max转Maya了。其他的栏目包装,广告等两者都可用到。最终没有谁好谁坏之分,只有用处不同,两软件都是相通的。以前国内学Maya的不多,因为没有中文版,教程也少,现在不一样了,教程也是不少了。

3d MAX和Maya各有其优势,用户可以根据自身的项目需求自主选择软件,其最大的区别就是3d MAX可以不费力气得到你想要的东西,Maya可能要花很多精力但是可以做出随心所欲的东西,灵活性要强。

Maya和3ds Max都可以做出如下效果(虽然这些模型本身是3ds Max做出来的):

擎天柱



大黄蜂


好了,认识到3D制作软件的神奇了,下面我们对本文的主角——X文件来一个介绍。



三、对X文件的认识

在Direct3D中,我们一般都采用.X文件来存储网格数据。X文件格式是微软定义的3D模式文件格式,其中包括网格的纹理,动画以及用户定义对象的一些数据等。需要注意的是,.X文件通常并未存储具体的纹理数据,它只包含纹理贴图的文件名,所以我们常常会发现,X文件会和一些图片文件比如.BMP、.DDS一同出没,放在一起,比如本次demo中我们使用的初音模型,除了.X文件本身miki.X之外,还有数十张BMP位图纹理图片配合着miki.X。

四、从3DS Max中导出.X文件方法详解


由于我们是初次接触三维模型文件的载入,当然得先学走,也就是先学Direct3D自带的.X模型文件的载入和使用,.X文件作为微软定义的3D模式文件格式,Direct3D的默认三维模型格式,Direct3D自然对其有着非常好的支持度,有大量的函数辅助着.X文件的使用。

至于学到后头了,我们就可以通过一些代码的编写(或者说使用游戏引擎的时候),直接在Direct3D中使用3ds、.max、.obj以及.mb等主流三维建模软件本身的文件。不过现在我们作为初学者,当前掌握好.X模型文件使用的各方各面,就能应付绝大多数场合了。

想要从3DS Max中导出.X文件,简单来说,有三个要素,我们分别来详细说一说。

第一个要素,当然是3DS Max软件的下载和安装。浅墨目前安装的是3DS Max 2012的32位版。大家可以百度一下,下载并安装,至于软件的破解,网上教程很多,这里就不具体讲了。

第二个要素,Panda插件的下载和配置。这个Panda插件熟话说就是能让3DS Max支持导出.X格式文件的一个插件。通常我们在3DS Max中打开一个3D模型后,点左上角的【主菜单】->【导出】之后,在弹出的导出选项框中并没有我们想要的.X文件。所以我们需要下载并配置一下与我们3DS Max 相对应的Panda插件,来让导出选项中有我们的.X文件,这样才能完成3DS中的模型到.X文件的导出。以便我们在我们的DirectX游戏程序中通过.X文件来载入好看的三维模型。

3DS Max 2012版本的Panda插件浅墨已经为大家准备好了,链接如下:

点击转到下载页面

下载之后,打开压缩包,把里面的对应安装版本位数的dle文件(32位对应X86字样,64位对应X64字样)放到3ds Max安装目录下的plugins之中,然后重启3ds Max即可。

例如浅墨自己的3ds Max2012安装在D盘的D:\Program Files\Autodesk\目录之下,那么对应的就在D:\Program Files\Autodesk\3ds Max 2012\plugins目录中添加dle插件文件,如图:


然后,重启一下3DS max 2012,我们就可以发现,导出选项中有了X文件导出项了。


第三个要素,也就是导出X文件的具体步骤了。

首先,正所谓巧妇难为无米之炊,我们需要有一个导出的对象,也就是一个三维的模型。模型嘛,可以自己用3ds Max现场做(想做出个像模像样的模型一时半会儿显然不现实,而且这一般都是美工童鞋的工作,我们可是写程序的筒子们),也可以自己去网上下载,这里推荐一个论坛,有大量的3D模型资源:http://www.cgmodel.cn/。比如我们在这个论坛下载了Dota英雄幻影刺客的.max模型。(记住目前主流的格式分.max和.mb两种,我们3ds Max用的是.max,而Maya用的是.mb,而另外一种.obj是万金油格式,3ds Max和Maya两者都可用,所以想在3ds Max与Maya之间通用的话,就用.obj)

好了,继续讲,我们下载好了Dota英雄幻影刺客的.max模型,解压缩包,点击HeroWarden.max模型文件,于是就打开了这个模型,如图。


然后,就像之前提到的,点击左上角的【 主菜单】->【导出】。如图:

这时就会弹出一个【选择要导出的文件】对话框。在弹出的这个对话框的【保存类型】中选择【Panda DirectX(*.X)】,然后自己取一个文件名(这里我们取名Warden,魔兽3中幻影刺客模型所对应的人物名称——守望者),设定好保存的路径,点【保存】。如图。


然后会弹出一个名为【PandaSoft DirectX Exporter】的窗口,在里面我们可以进行一些导出模型参数的设定和修改。如下:


如果没有什么需要特殊设置的,点【确定】就行了。

然后经过3ds Max的处理,我们就发现在我们设定的保存路径下多了.X文件以及配套的纹理了。

这样,如果我们想要在我们写的游戏程序中使用这个幻影刺客(守望者)的三维模型,把这两个文件一起放到我们工程中相应的地方就可以了。

之前讲了那么多无非是在为.X文件的诞生造势,下面就开始隆重介绍如何在Direct3D程序中载入X文件的具体知识吧~

而想利用.X文件来在游戏程序中载入三维模型的话,首先就需要将.X文件中的各种数据分别加载到内存中,而这些数据主要包括顶点数据、材质数据和纹理数据等等。首先,我们需要介绍一下与网格模型相关的一个重要的接口——ID3DXMESH。


五、网格模型接口ID3DXMESH


在Direct3D中,微软为我们提供了ID3DXMesh接口表示网格,这个接口继承自ID3DXBaseMesh接口。网格模型接口ID3DXMesh实际上是三维物体的顶点缓存的集合,他将为我们创建顶点缓存、定义灵活顶点格式和绘制顶点缓冲区等功能封装在一个COM对象中,这样复杂三维物体的绘制就显得非常简便了。

其中,ID3DXMESH接口中的D3DXCreateMesh()可用于创建一个Direct3D网格模型对象,我们可以在MSDN中查到该函数声明是这样的:

HRESULT D3DXCreateMesh(
 _In_   DWORD NumFaces,
 _In_   DWORD NumVertices,
 _In_   DWORD Options,
 _In_   const LPD3DVERTEXELEMENT9*pDeclaration,
 _In_   LPDIRECT3DDEVICE9pD3DDevice,
 _Out_  LPD3DXMESH *ppMesh
);

然后开始介绍一下这个函数。

■ 第一个参数, DWORD类型的NumFaces,表示创建网格模型的多边形数目。

■ 第二个参数, DWORD类型的NumVertices,表示创建网格的顶点数目。

■ 第三个参数, DWORD类型的Options,表示创建网格时的附加选项,他的取值为D3DXMESH枚举体中的一个或者多个值。这个枚举体定义如下:

typedef enum D3DXMESH {
  D3DXMESH_32BIT                   = 0x001,
  D3DXMESH_DONOTCLIP               = 0x002,
  D3DXMESH_POINTS                  = 0x004,
  D3DXMESH_RTPATCHES               = 0x008,
  D3DXMESH_NPATCHES                = 0x4000,
  D3DXMESH_VB_SYSTEMMEM            = 0x010,
  D3DXMESH_VB_MANAGED              = 0x020,
  D3DXMESH_VB_WRITEONLY            = 0x040,
  D3DXMESH_VB_DYNAMIC              = 0x080,
 D3DXMESH_VB_SOFTWAREPROCESSING   =0x8000,
  D3DXMESH_IB_SYSTEMMEM            = 0x100,
  D3DXMESH_IB_MANAGED              = 0x200,
  D3DXMESH_IB_WRITEONLY            = 0x400,
  D3DXMESH_IB_DYNAMIC              = 0x800,
 D3DXMESH_IB_SOFTWAREPROCESSING   =0x10000,
  D3DXMESH_VB_SHARE                = 0x1000,
  D3DXMESH_USEHWONLY               = 0x2000,
  D3DXMESH_SYSTEMMEM               = 0x110,
  D3DXMESH_MANAGED                 = 0x220,
  D3DXMESH_WRITEONLY               = 0x440,
  D3DXMESH_DYNAMIC                 = 0x880,
 D3DXMESH_SOFTWAREPROCESSING      =0x18000
} D3DXMESH, *LPD3DXMESH;


一般情况下,我们都把这个Options取为D3DXMESH_SYSTEMMEM或者D3DXMESH_MANAGED,表示对Direct3D顶点缓冲区和索引缓冲区使用D3DPOOL_SYSTEMMEM或者D3DPOOL_MANAGED内存。

■ 第四个参数, const LPD3DVERTEXELEMENT9类型的*pDeclaration,表示顶点包含哪些信息。这个参数的作用类似于我们之间一直在用的灵活顶点格式(FVF),表示顶点包含了哪些具体数据,但是它却高于灵活顶点格式。它的类型LPD3DVERTEXELEMENT9表示顶点元素,主要用于我们来没讲到的可编程渲染流水线之中,在此我们暂且不用去多做考虑。

■ 第五个参数, LPDIRECT3DDEVICE9类型的pD3DDevice,就是我们的金钥匙,Direct3D设备的指针了。

■ 第六个参数, LPD3DXMESH类型的*ppMesh,指向我们创建好的网格模型对象指针的地址,用于返回创建好的网格模型对象。可以说我们调用D3DXCreateMesh就是为了创建并得到这个指针地址。后面关于我们创建好的网格模型的访问,都靠这个ppMesh参数了。

需要注意的是,这个创建好网格模型对象的D3DXCreateMesh函数我们通常很少直接去用它,而且直接用它往往也意义不大。它总是“真人不露相”,被封装在了其他Direct3D函数之中,默默地为我们服务。

介绍完网格模型接口ID3DXMESH相关的知识,下面就来看看从X文件载入模型的具体步骤。

六、X文件模型载入三步曲

Ⅰ.三步曲之一:通过.X文件加载网格模型

上面讲到了D3DXCreateMesh函数很少去直接应用,因为我们常常都是通过载入.X文件来生成网格模型的,用到的函数是D3DXLoadMeshFromX。我们可以在MSDN中查到这个函数的声明如下:

HRESULT D3DXLoadMeshFromX(
  _In_   LPCTSTR pFilename,
  _In_   DWORD Options,
  _In_   LPDIRECT3DDEVICE9 pD3DDevice,
  _Out_  LPD3DXBUFFER *ppAdjacency,
  _Out_  LPD3DXBUFFER *ppMaterials,
  _Out_  LPD3DXBUFFER *ppEffectInstances,
  _Out_  DWORD *pNumMaterials,
  _Out_  LPD3DXMESH *ppMesh
);

■ 第一个参数, LPCTSTR类型的pFilename,显然就是一个指向我们需要加载的.X文件的磁盘路径和文件名的字符串了。

■ 第二个参数, DWORD类型的Options,表示创建网格时的附加选项,他的取值为D3DXMESH枚举体中的一个或者多个值。这个参数我们刚才在讲D3DXCreateMesh时已经讲过了,具体参看D3DXCreateMesh的第三个参数,在这里就不赘述了。

■ 第三个参数,LPDIRECT3DDEVICE9类型的pD3DDevice,也就是我们的金钥匙,Direct3D设备的指针。

■ 第四个参数,LPD3DXBUFFER类型的*ppAdjacency,用于保存加载网格的邻接信息,也就是包含每个多边形周围的多边形信息的缓冲区的内存地址。

■ 第五个参数,LPD3DXBUFFER类型的*ppMaterials,用于保存网格的所有子集的材质,指向用于存储模型材质和纹理文件名的缓冲区的地址,而材质的数目存在之后第七个参数pNumMaterials中了。

■ 第六个参数,LPD3DXBUFFER类型的*ppEffectInstances,用于存储网格模型的特殊效果,指向用于存储模型效果实例的缓冲区的内存地址,这个参数通常设为NULL就可以了。

■ 第七个参数,DWORD类型的*pNumMaterials,它配合着第五个参数,用于存储所有子集材质的数目。

■ 第八个参数,LPD3DXMESH类型的*ppMesh,指向我们从文件生成的Direct3D网格模型指针的地址。可以说我们调用D3DXLoadMeshFromX就是为了从文件加载X文件的模型信息并进行模型的创建,从而能得到这个指向创建好的模型的指针地址。后面关于我们创建好的网格模型的访问,都靠这个ppMesh参数了。

我们应该可以发现,在上面我们的D3DXLoadMeshFromX函数中引入了一个新的Direct3D类型,他就是LPD3DXBUFFER。LPD3DXBUFFER因数据操作的方便性而诞生,我们称它为泛型数据结构。它的好处是可以存储顶点位置坐标、材质、纹理等多种类型的Direct3D数据,而不必对每种数据都去声明一种函数接口类型。可使用接口函数ID3DXBuffer::GetBufferPointer()获取缓冲区中的数据,使用ID3DXBuffer::GetBufferSize()获得缓冲区数据大小、这两个接口函数的声明如下:

LPVOID GetBufferPointer();
DWORD GetBufferSize();

没错,这就是原型声明,因为这两个函数都没有参数,所以他们的身子显得非常单薄。

比如,我们要从网格模型中提取材质属性和纹理文件名,那么代码就是像这样写:

D3DXMATERIAL *pMtrls =(D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer();


Ⅱ. 三步曲之二:载入材质和纹理

如果之前的D3DXLoadMeshFromX函数调用成功的话,那么参数ppMaterials就会获得.X文件中三维模型的材质和纹理等信息,而pNumMaterials参数就会获得材质的数目。

X文件中的材质信息是以D3DXMATERIAL结构类型的数组形式储存的。其中,该结构定义了D3DMATERIAL9结构类型的成员和一个指向以NULL结尾的字符串指针,而该字符串用于指定与网格子集相关的纹理贴图文件名。我们可以在MSDN中查到D3DXMATERIAL结构体的定义如下:

typedef struct D3DXMATERIAL {
  D3DMATERIAL9 MatD3D;   //材质信息
  LPSTR        pTextureFilename; //纹理贴图文件
} D3DXMATERIAL, *LPD3DXMATERIAL;


当我们加载X文件后,需要遍历整个D3DXMATERIAL结构类型的数组,用于取出保存在ID3DXBuffer接口对象中的材质信息。由于X文件中并未存储具体的纹理数据,它只包含纹理贴图的文件名,因此需要我们自己根据该文件名创建相应的纹理对象。就像这样:

// 从X文件中加载网格数据
  LPD3DXBUFFER pAdjBuffer  = NULL;
  LPD3DXBUFFER pMtrlBuffer =NULL;
 
  D3DXLoadMeshFromX(L"miki.X",D3DXMESH_MANAGED, g_pd3dDevice,
        &pAdjBuffer,&pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh);
 
  // 读取材质和纹理数据
  D3DXMATERIAL *pMtrls =(D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息
  g_pMaterials = newD3DMATERIAL9[g_dwNumMtrls];
  g_pTextures  = new LPDIRECT3DTEXTURE9[g_dwNumMtrls];
 
  for (DWORD i=0;i



Ⅲ. 三步曲之三:绘制网格模型


完成前两步做好准备工作之后,也就是生成X文件网格和材质和纹理的读取之后,接下来就是把我们准备的内容绘制出来就行了。我们依然是用ID3DXMesh接口的DrawSubset方法绘制网格中的每个子集的。但是由于绘制的部分比较多,对每个部分的绘制,我们都需要专门为其进行材质和纹理的设置,然后才进行绘制,所以一般我们在绘制从X文件读取的三维模型的时候,一般用一个for循环来进行绘制,就像这样:

g_pd3dDevice->BeginScene();                     // 开始绘制
     
      //用一个for循环,进行网格各个部分的绘制
      for(DWORD i = 0; i < g_dwNumMtrls; i++)
      {
           g_pd3dDevice->SetMaterial(&g_pMaterials[i]);
           g_pd3dDevice->SetTexture(0,g_pTextures[i]);
           g_pMesh->DrawSubset(i);
      }
g_pd3dDevice->EndScene();                       // 结束绘制



Ⅳ.总结与升华

做一下总结,从X文件读取模型并进行绘制其实很简单,就三步工作,简明扼要十二个字,三步曲:

加载网格,加载材质纹理,绘制

核心代码如下:

// 三步曲之一,从X文件中加载网格数据
      LPD3DXBUFFERpAdjBuffer  = NULL;
      LPD3DXBUFFERpMtrlBuffer = NULL;
 
      D3DXLoadMeshFromX(L"miki.X",D3DXMESH_MANAGED, g_pd3dDevice,
           &pAdjBuffer,&pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh);
 
 
      //三步曲之二,读取材质和纹理数据
      D3DXMATERIAL*pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息
      g_pMaterials= new D3DMATERIAL9[g_dwNumMtrls];
      g_pTextures  = new LPDIRECT3DTEXTURE9[g_dwNumMtrls];
 
      for(DWORD i=0; iClear(0,NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(100, 100, 100), 1.0f, 0);
//三步曲之三,绘制
g_pd3dDevice->BeginScene();                     // 开始绘制
     
      //用一个for循环,进行网格各个部分的绘制
      for(DWORD i = 0; i < g_dwNumMtrls; i++)
      {
           g_pd3dDevice->SetMaterial(&g_pMaterials[i]);
           g_pd3dDevice->SetTexture(0,g_pTextures[i]);
           g_pMesh->DrawSubset(i);
      }
g_pd3dDevice->EndScene();                       // 结束绘制
g_pd3dDevice->Present(NULL, NULL, NULL,NULL);  // 翻转与显示
       
}


七、详细注释的配套源代码欣赏


本篇文章的配套源代码依旧是包含四个文件,主要用于公共辅助宏定义的D3DUtil.h,用于封装了DirectInput输入控制API的DirectInputClass.h和DirectInputClass.cpp最后才是核心代码main.cpp。

其实D3DUtil.h,DirectInputClass.h以及DirectInputClass.cpp在上篇文章的配套demo的基础上并没有做任何修改,我们只是修改了main.cpp中的代码而已。但是为了大家的观看方便,浅墨依旧是把这些代码都依次贴出来。

此篇文章中我们的三维模型的素材文件选的是初音的战斗装(本来这次是想选diablo3中diablo的,但是那个看起来太暴力了,就选的这个初音)。如图:


好了,下面我们就依次贴出四个文件的代码,大家重点看放在最后的main.cpp就可以了:


1.D3DUtil.h

//*****************************************************************************************
// Desc: D3DUtil.h头文件,用于公共辅助宏的定义
// 2013年 2月03日  Create by 浅墨 
//*****************************************************************************************

#pragma once


#ifndef HR
#define HR(x)    { hr = x; if( FAILED(hr) ) { return hr; } }         //自定义一个HR宏,方便执行错误的返回
#endif

#ifndef SAFE_DELETE					
#define SAFE_DELETE(p)       { if(p) { delete (p);     (p)=NULL; } }       //自定义一个SAFE_RELEASE()宏,便于指针资源的释放
#endif    

#ifndef SAFE_RELEASE			
#define SAFE_RELEASE(p)      { if(p) { (p)->Release(); (p)=NULL; } }     //自定义一个SAFE_RELEASE()宏,便于COM资源的释放
#endif



然后是封装了DirectInput的类DInputClass的头文件:

2.DirectInputClass.h

//=============================================================================
// Name: DirectInputClass.h
//	Des: 封装了DirectInput键盘输入处理类的头文件
// 2013年 2月03日  Create by 浅墨 
//=============================================================================
#pragma once
#include "D3DUtil.h"
#define DIRECTINPUT_VERSION 0x0800
#include 

//DInputClass类定义开始
class DInputClass
{
private:
	IDirectInput8					* m_pDirectInput;   //IDirectInput8接口对象
	IDirectInputDevice8		* m_KeyboardDevice;  //键盘设备接口对象
	char								m_keyBuffer[256];       //用于键盘键值存储的数组

	IDirectInputDevice8		*m_MouseDevice;      //鼠标设备接口对象
	DIMOUSESTATE				m_MouseState;			//用于鼠标键值存储的一个结构体

public:
	HRESULT		Init( HWND hWnd,HINSTANCE hInstance,DWORD keyboardCoopFlags, DWORD mouseCoopFlags ); //初始化DirectInput键盘及鼠标输入设备
	void				GetInput();   //用于获取输入信息的函数
	bool				IsKeyDown(int iKey);   //判断键盘上某键是否按下
	
	bool				IsMouseButtonDown(int button);  //判断鼠标按键是否被按下
	float				MouseDX();   //返回鼠标的X轴坐标值
	float				MouseDY();	//返回鼠标的Y轴坐标值
	float				MouseDZ();	//返回鼠标的Z轴坐标值


public:
	DInputClass(void);		//构造函数
	~DInputClass(void);	//析构函数
};


接着是封装了DirectInput的类DInputClass的头文件:

3.DirectInputClass.cpp

//=============================================================================
// Desc: DirectInput键盘输入处理类源文件
// 2013年 2月03日  Create by 浅墨 
//=============================================================================
#include "DirectInputClass.h"


//-----------------------------------------------------------------------------
// Desc: 构造函数
//-----------------------------------------------------------------------------
DInputClass::DInputClass()
{
	m_pDirectInput = NULL;
	m_KeyboardDevice = NULL;
	ZeroMemory(m_keyBuffer,sizeof(char)*256);
	m_MouseDevice= NULL;
	ZeroMemory(&m_MouseState, sizeof(m_MouseState));
}


//-----------------------------------------------------------------------------
// Name:DInputClass::Init()
// Desc: 初始化DirectInput键盘及鼠标输入设备
//-----------------------------------------------------------------------------
HRESULT DInputClass::Init( HWND hWnd,HINSTANCE hInstance,DWORD keyboardCoopFlags, DWORD mouseCoopFlags )
{
	HRESULT hr;
	//初始化一个IDirectInput8接口对象
	HR(DirectInput8Create( hInstance, DIRECTINPUT_VERSION, 
		IID_IDirectInput8,(void**)&m_pDirectInput,NULL ));

	//进行键盘设备的初始化
	HR( m_pDirectInput->CreateDevice( GUID_SysKeyboard, &m_KeyboardDevice, NULL ));
	HR( m_KeyboardDevice->SetCooperativeLevel( hWnd, keyboardCoopFlags));
	HR( m_KeyboardDevice->SetDataFormat( &c_dfDIKeyboard ));
	HR( m_KeyboardDevice->Acquire( ));
	HR( m_KeyboardDevice->Poll( ));

	//进行鼠标设备的初始化
	HR( m_pDirectInput->CreateDevice( GUID_SysMouse, &m_MouseDevice, NULL ));
	HR( m_MouseDevice->SetCooperativeLevel( hWnd,mouseCoopFlags));
	HR( m_MouseDevice->SetDataFormat( &c_dfDIMouse ));
	HR( m_MouseDevice->Acquire( ));
	HR( m_KeyboardDevice->Poll( ));

	return S_OK;
}


//-----------------------------------------------------------------------------
// Name:DInputClass::GetInput()
// Desc: 用于获取输入信息的函数
//-----------------------------------------------------------------------------
void DInputClass::GetInput()
{
	HRESULT hr = m_KeyboardDevice->GetDeviceState(sizeof(m_keyBuffer), (void**)&m_keyBuffer); 
	//获取键盘输入消息
	if(hr)
	{
		m_KeyboardDevice->Acquire();  
		m_KeyboardDevice->GetDeviceState( sizeof(m_keyBuffer),(LPVOID)m_keyBuffer );
	}

	hr = m_MouseDevice->GetDeviceState(sizeof(DIMOUSESTATE), (void**)&m_MouseState); 
	//获取鼠标输入消息
	if(hr)
	{
		m_MouseDevice->Acquire();
		m_MouseDevice->GetDeviceState( sizeof(DIMOUSESTATE), (void**)&m_MouseState);
	}
}

//-----------------------------------------------------------------------------
// Name:DInputClass::IsKeyDown()
// Desc: 判断键盘上某个键是否按下
//-----------------------------------------------------------------------------
bool DInputClass::IsKeyDown(int iKey)
{
	if(m_keyBuffer[iKey] & 0x80)
		return true;
	else
		return false;
}


//-----------------------------------------------------------------------------
// Name:DInputClass::IsMouseButtonDown()
// Desc: 判断鼠标上某键是否按下
//-----------------------------------------------------------------------------
bool DInputClass::IsMouseButtonDown(int button)
{
	return (m_MouseState.rgbButtons[button] & 0x80) != 0;
}

//-----------------------------------------------------------------------------
// Name:DInputClass::MouseDX
// Desc: 返回鼠标指针的X轴坐标值
//-----------------------------------------------------------------------------
float DInputClass::MouseDX()
{
	return (float)m_MouseState.lX;
}

//-----------------------------------------------------------------------------
// Name:DInputClass::MouseDY
// Desc: 返回鼠标指针的Y轴坐标值
//-----------------------------------------------------------------------------
float DInputClass::MouseDY()
{
	return (float)m_MouseState.lY;
}

//-----------------------------------------------------------------------------
// Name:DInputClass::MouseDZ
// Desc: 返回鼠标指针的Z轴坐标值(滚轮)
//-----------------------------------------------------------------------------
float DInputClass::MouseDZ()
{
	return (float)m_MouseState.lZ;
}




//-----------------------------------------------------------------------------
// Desc: 析构函数
//-----------------------------------------------------------------------------
DInputClass::~DInputClass(void)
{
	if(m_KeyboardDevice != NULL)
		m_KeyboardDevice->Unacquire();
	if(m_MouseDevice != NULL)
		m_MouseDevice->Unacquire();
	SAFE_RELEASE(m_KeyboardDevice);
	SAFE_RELEASE(m_MouseDevice);
	SAFE_RELEASE(m_pDirectInput);
}



最后就是核心代码了:

4.main.cpp

//*****************************************************************************************
//
//【Visual C++】游戏开发笔记系列配套源码四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观
//		 VS2010版
// 2013年 2月17日  Create by 浅墨 
//图标及图片素材: 《仙剑奇侠传五前传》 龙溟
//
//***************************************************************************************** 


//*****************************************************************************************
// Desc: 宏定义部分   
//*****************************************************************************************
#define SCREEN_WIDTH	800						//为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define SCREEN_HEIGHT	600							//为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE	_T("【Visual C++】游戏开发笔记系列配套源码四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观") //为窗口标题定义的宏



//*****************************************************************************************
// Desc: 头文件定义部分  
//*****************************************************************************************                                                                                       
#include 
#include 
#include 
#include  
#include "DirectInputClass.h"



//*****************************************************************************************
// Desc: 库文件定义部分  
//***************************************************************************************** 
#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") 




//*****************************************************************************************
// Desc: 全局变量声明部分  
//*****************************************************************************************
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;   //世界矩阵
DInputClass*								g_pDInput = NULL;         //一个DInputClass类的指针

LPD3DXMESH          g_pMesh     = NULL; // 网格对象
D3DMATERIAL9*       g_pMaterials    = NULL; // 网格的材质信息
LPDIRECT3DTEXTURE9* g_pTextures     = NULL; // 网格的纹理信息
DWORD               g_dwNumMtrls    = 0;    // 材质的数目

//*****************************************************************************************
// Desc: 全局函数声明部分 
//***************************************************************************************** 
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);
void								Direct3D_Update( HWND hwnd);
void								Direct3D_CleanUp( );
float								Get_FPS();
void								Matrix_Set();


//*****************************************************************************************
// Name: WinMain( )
// Desc: 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("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, SCREEN_WIDTH,
		SCREEN_HEIGHT, NULL, NULL, hInstance, NULL );


	//Direct3D资源的初始化,调用失败用messagebox予以显示
	if (!(S_OK==Direct3D_Init (hwnd,hInstance)))
	{
		MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0); //使用MessageBox函数,创建一个消息窗口 
	}

	

	MoveWindow(hwnd,200,50,SCREEN_WIDTH,SCREEN_HEIGHT,true);   //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,50)处
	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循环
	{
		if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )   //查看应用程序消息队列,有消息时将队列中的消息派发出去。
		{
			TranslateMessage( &msg );		//将虚拟键消息转换为字符消息
			DispatchMessage( &msg );		//该函数分发一个消息给窗口程序。
		}
		else
		{
			Direct3D_Update(hwnd);         //调用更新函数,进行画面的更新
			Direct3D_Render(hwnd);			//调用渲染函数,进行画面的渲染			
		}
	}

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



//*****************************************************************************************
// Name: WndProc()
// Desc: 对窗口消息进行处理
//*****************************************************************************************
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )   //窗口过程函数WndProc
{
	switch( message )				//switch语句开始
	{
	case WM_PAINT:					 // 客户区重绘消息
		Direct3D_Render(hwnd);          //调用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;					//正常退出
}


//*****************************************************************************************
// Name: Direct3D_Init( )
// Desc: 初始化Direct3D
// Point:【Direct3D初始化四步曲】
//		1.初始化四步曲之一,创建Direct3D接口对象
//		2.初始化四步曲之二,获取硬件设备信息
//		3.初始化四步曲之三,填充结构体
//		4.初始化四步曲之四,创建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            = SCREEN_WIDTH;
	d3dpp.BackBufferHeight           = SCREEN_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;
}


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); 



	// 从X文件中加载网格数据
	LPD3DXBUFFER pAdjBuffer  = NULL;
	LPD3DXBUFFER pMtrlBuffer = NULL;

	D3DXLoadMeshFromX(L"miki.X", D3DXMESH_MANAGED, g_pd3dDevice, 
		&pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh);

	// 读取材质和纹理数据
	D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息
	g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls];
	g_pTextures  = new LPDIRECT3DTEXTURE9[g_dwNumMtrls];

	for (DWORD i=0; iSetLight(0, &light);
	g_pd3dDevice->LightEnable(0, true);
	g_pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true);
	g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, true);

	// 设置渲染状态
	g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);   //开启背面消隐

	return S_OK;
}


//*****************************************************************************************
// Name:Matrix_Set()
// Desc: 设置世界矩阵
// Point:【Direct3D四大变换】
//		1.【四大变换之一】:世界变换矩阵的设置
//		2.【四大变换之二】:取景变换矩阵的设置
//		3.【四大变换之三】:投影变换矩阵的设置
//		4.【四大变换之四】:视口变换的设置
//*****************************************************************************************
void Matrix_Set()
{
	//--------------------------------------------------------------------------------------
	//【四大变换之一】:世界变换矩阵的设置
	//--------------------------------------------------------------------------------------


	//--------------------------------------------------------------------------------------
	//【四大变换之二】:取景变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matView; //定义一个矩阵
	D3DXVECTOR3 vEye(0.0f, 0.0f, -250.0f);  //摄像机的位置
	D3DXVECTOR3 vAt(0.0f, 0.0f, 0.0f); //观察点的位置
	D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f);//向上的向量
	D3DXMatrixLookAtLH(&matView, &vEye, &vAt, &vUp); //计算出取景变换矩阵
	g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //应用取景变换矩阵

	//--------------------------------------------------------------------------------------
	//【四大变换之三】:投影变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matProj; //定义一个矩阵
	D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f,(float)((double)SCREEN_WIDTH/SCREEN_HEIGHT),1.0f, 1000.0f); //计算投影变换矩阵
	g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);  //设置投影变换矩阵

	//--------------------------------------------------------------------------------------
	//【四大变换之四】:视口变换的设置
	//--------------------------------------------------------------------------------------
	D3DVIEWPORT9 vp; //实例化一个D3DVIEWPORT9结构体,然后做填空题给各个参数赋值就可以了
	vp.X      = 0;		//表示视口相对于窗口的X坐标
	vp.Y      = 0;		//视口相对对窗口的Y坐标
	vp.Width  = SCREEN_WIDTH;	//视口的宽度
	vp.Height = SCREEN_HEIGHT; //视口的高度
	vp.MinZ   = 0.0f; //视口在深度缓存中的最小深度值
	vp.MaxZ   = 1.0f;	//视口在深度缓存中的最大深度值
	g_pd3dDevice->SetViewport(&vp); //视口的设置

}


void				Direct3D_Update( HWND hwnd)
{
	//使用DirectInput类读取数据
	g_pDInput->GetInput();

	// 按住鼠标左键并拖动,为平移操作
	static FLOAT fPosX = 0.0f, fPosY = 0.0f, fPosZ = 0.0f;
	
	if (g_pDInput->IsMouseButtonDown(0)) 
	{
		fPosX += (g_pDInput->MouseDX())*  0.08f;
		fPosY += (g_pDInput->MouseDY()) * -0.08f;
	}

	//鼠标滚轮,为观察点收缩操作
	fPosZ += (g_pDInput->MouseDZ())* 0.02f;

	// 平移物体
	if (g_pDInput->IsKeyDown(DIK_A)) fPosX -= 0.005f;
	if (g_pDInput->IsKeyDown(DIK_D)) fPosX += 0.005f;
	if (g_pDInput->IsKeyDown(DIK_W)) fPosY += 0.005f;
	if (g_pDInput->IsKeyDown(DIK_S)) fPosY -= 0.005f;


	D3DXMatrixTranslation(&g_matWorld, fPosX, fPosY, fPosZ);


	// 按住鼠标右键并拖动,为旋转操作
	static float fAngleX = 0, fAngleY =0;
	
	if (g_pDInput->IsMouseButtonDown(1)) 
	{
		fAngleX += (g_pDInput->MouseDY())* -0.01f;
		fAngleY += (g_pDInput->MouseDX()) * -0.01f;
	}
	// 旋转物体
 	if (g_pDInput->IsKeyDown(DIK_UP)) fAngleX += 0.005f;
	if (g_pDInput->IsKeyDown(DIK_DOWN)) fAngleX -= 0.005f;
 	if (g_pDInput->IsKeyDown(DIK_LEFT)) fAngleY -= 0.005f;
 	if (g_pDInput->IsKeyDown(DIK_RIGHT)) fAngleY += 0.005f;


	D3DXMATRIX Rx, Ry;
	D3DXMatrixRotationX(&Rx, fAngleX);
	D3DXMatrixRotationY(&Ry, fAngleY);

	g_matWorld = Rx * Ry * g_matWorld;
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_matWorld);
	Matrix_Set();
}



//*****************************************************************************************
// Name: Direct3D_Render()
// Desc: 进行图形的渲染操作
// Point:【Direct3D渲染五步曲】
//		1.渲染五步曲之一,清屏操作
//		2.渲染五步曲之二,开始绘制
//		3.渲染五步曲之三,正式绘制
//		4.渲染五步曲之四,结束绘制
//		5.渲染五步曲之五,翻转显示
//*****************************************************************************************

void Direct3D_Render(HWND hwnd)
{

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之一】:清屏操作
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(100, 150, 100), 1.0f, 0);

	//定义一个矩形,用于获取主窗口矩形
	RECT formatRect;
	GetClientRect(hwnd, &formatRect);

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

	// 用一个for循环,进行网格各个部分的绘制
	for (DWORD i = 0; i < g_dwNumMtrls; i++)
	{
		g_pd3dDevice->SetMaterial(&g_pMaterials[i]);
		g_pd3dDevice->SetTexture(0, g_pTextures[i]);
		g_pMesh->DrawSubset(i);
	}

			//在窗口右上角处,显示每秒帧数
			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.top = 30;
			static wchar_t strInfo[256] = {0};
			swprintf_s(strInfo,-1, L"模型坐标: (%.2f, %.2f, %.2f)", g_matWorld._41, g_matWorld._42, g_matWorld._43);
			g_pTextHelper->DrawText(NULL, strInfo, -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(135,239,136,255));

			// 输出帮助信息
			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"    按住鼠标左键并拖动:平移模型", -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"    滑动鼠标滚轮:拉伸模型", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
			formatRect.top += 25;
			g_pTextHelper->DrawText(NULL, L"    W、S、A、D键:平移模型 ", -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"    ESC键 : 退出程序", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));


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




//*****************************************************************************************
// Name:Get_FPS()函数
// Desc: 用于计算帧速率
//*****************************************************************************************
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;
}



//*****************************************************************************************
// Name: Direct3D_CleanUp()
// Desc: 对Direct3D的资源进行清理,释放COM接口对象
//*****************************************************************************************
void Direct3D_CleanUp()
{
	//释放COM接口对象
	for (DWORD i = 0; i


上面代码的运行截图如下,可以发现这个X文件中存放的模型的细节非常的细腻:











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

本节笔记配套源代码请点击这里下载:

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




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


当处在低谷时一定要清醒,要忍耐,要淡定。这是生活给你一个难得的自省机会,利用这些时间多学习暗地里提高自己,为即将到来的高峰做准备! 祝大家新年快乐,迎接又一个崭新的一年~~




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

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


你可能感兴趣的:(【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观...)