【Visual C++】游戏开发笔记三十八 浅墨DirectX提高班之六 携手迈向三维世界:四大变换展身手...

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

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

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

本篇文章里,我们首先对Direct3D中固定功能渲染流水线相关概念进行了深入的剖析,然后介绍了创建三维游戏世界的四大变换的概念和使用的方方面面,最后依旧是提供文章配套的详细注释的demo源代码的欣赏,并在文章末尾提供了源代码下载。

从本篇文章开始我们就来开始来学习固定渲染流水线这套渲染体系。

其实固定渲染流水线和之后我们要学习的可编程渲染流水线体系有很多异曲同工之妙,所以先学习固定功能渲染流水线体系,再学可编程渲染流水线体系,就可以循序渐进,步步为营地掌握好DirectX。

空间中的物体需要使用三维坐标来描述,而我们的显示器是显然是二维的,所以在屏幕上渲染一个三维场景时,首先需要将物体描述空间物体的三维坐标变换为二维坐标(也就是世界坐标到屏幕坐标),这在Direct3D中称为顶点坐标变换。顶点坐标变换通常通过矩阵来完成。我们之前的几个demo中,演示的是如何显示一个二维的平面图形,它的顶点是以屏幕坐标系的二维值表示的,也是经过顶点坐标变换之后的顶点坐标数据,可以把顶点坐标变换想象成摄像的过程,三维世界的景物通过摄像机拍摄显示在二维的相片之上,有所不同的是把相片换成了屏幕。

文章开头,我们先来看一下固定功能渲染流水线这套渲染体系的核心思想。

一、固定功能渲染流水线概述

在固定功能渲染流水线这套体系中,大体分为两个阶段,第一阶段我们将它称为坐标变换和光照处理阶段(Transforming &Lighting,简称T&L阶段)。在这个阶段中,每个对象的顶点从一个抽象的、浮点坐标变换到基于像素的屏幕空间当中。这里需要注意的是,坐标变换不仅包含物体顶点位置,它还可能包括顶点的法线、纹理坐标等等。并根据场景中光源和物体表面的材质对物体顶点应用不同类型的光照效果。还有其他一些比较重要的任务,比如视口的设置和裁剪也是在第一阶段进行的。讲完第一阶段,我们再来看看第二阶段,第二阶段称为光栅化处理阶段。顶点在经过第一阶段也就是变换与光照阶段的“洗礼”之后,已经略有雏形,在第二阶段,Direct3D将这些已经完成变换和光照阶段的顶点组织为以点、线、面为基础的图元,应用纹理贴图和物体顶点的颜色属性,并根据相关渲染状态的设置(比如着色模式等)

,决定每个像素最终的颜色值,并且在屏幕上显示出来。为了大家更宏观和更深入的理解,浅墨依然是配了一幅图。通过这幅图,大家可以对固定功能渲染流水线的结构脉络做到一目了然。

需要注意的是,渲染流水线中的步骤并不一定都要有的,根据实际情况可以省略一些。比如之前我们给出的几个demo,都是省略了变换和光照阶段,直接将顶点作为屏幕坐标输出显示。因为我们在定义顶点的属性的时候,给我们的顶点定的“标签”是D3DFVF_XYZRHW,表示包含经过坐标变换的顶点坐标值,这样Direct3D就知道这些顶点坐标不需要再经过顶点坐标变换了,他们的坐标值就是最终显示屏幕上的坐标值了。

目前我们重点介绍坐标变换和光照处理阶段(Transforming &Lighting,简称T&L阶段),也就是T&L阶段。在这个过程中,未经过变换和光照的顶点从一端进入,在流水线内部这些顶点将完成几个连续的操作,这几个操作按顺序分别为世界变换,取景变换,光照处理,投影变换以及视口变换。经过这些处理之后的顶点从另一端出来,表示已经完成坐标变换和光照处理了。我们的应用程序是通过指定几个矩阵、视口以及所使用的光线来建立T&L流水线的,然后应用程序将顶点送入流水线,并对这些顶点在流水线中进行坐标变换、照明以及裁剪,将其投影到屏幕空间当中,并根据视口的规定对其进行缩放。顶点在T&L流水线中进过“涅槃”之后,就可以去到第二阶段——光栅化处理阶段去完成新的试炼了。

关于变换和光照渲染流水线阶段,也就是T&L阶段,浅墨也配了一幅图,方便大家的记忆与理解:

二、携手迈向三维世界:四大变换显身手

首先来讲讲周边的概念。

在Direct3D中,如果我们未进行任何空间坐标变换而来绘制图形的话,图形将始终处于应用程序窗口的中心位置,在默认情况下这个位置就会成为世界坐标系的原点(0,0,0)。另外,我们也不能改变观察图形的视角方向。默认情况下的观察方向是世界坐标系的z轴正方向,也就是垂直程序窗口并向里观察的方向。之前我们的几个demo都是默认的z轴正方向为观察方向的。

而为了迈向三维世界,为了能够随心所欲地在三维世界中绘制和观察游戏画面,就是四大变换的show time了。

所谓四大变换,其实是浅墨自己在学习Direct3D的时候自己归纳的一个东西- -,也就是世界变换,取景变换,投影变换和视口变换的总称。

下面我们先来大致概括一下这四大变换的用途。

1. 为了能在世界空间中的指定位置来绘制图形,就需要在绘制图形前进行四大变换之一的世界变换运算。

2. 为了以不同的视角观察图形,就需要用到四大变换之二的取景变换运算。

3. 为了将相对较远的图形投影到同一个平面上并体现出“近大远小”的真实视觉效果,就需要用到四大变换之三的投影变换。

4. 为了控制显示图形的窗口的大小,比例以及深度等等信息,就要用到四大变换之四的视口变换。

下面我们就开始分别展开讲解这四大变换的细节知识。在讲解之前,有必要先跟大家说明一下。这里的四大变换的前三都是以矩阵作为载体的。这四大变换相关知识中或多或少会涉及到矩阵、向量,平面和射线等相关的数学知识,这些数学知识浅墨不准备专门花篇幅讲,因为讲起来都是概念,漫无目的,而且篇幅很长或许会花费几次更新的时间,况且写出来的东西肯定没新意而且没劲,所以没有写出来的必要。如果去学习Direct3D这款API的话,大可不必去纠结那么多数学概念。对矩阵和向量稍微有些了解就足够了,余下的高中数学(高中数学中没有矩阵相关的知识,所以矩阵相关概念需要另外学习)完全可以对付。如果实际过程中有不理解的地方再有的放矢地去查书和资料,这样学习起来会轻松很多,学习效率也会高得多。关于这方面数学知识的查阅资料,可以参看Direct3D 9龙书《Direct3D 9.0 3D游戏开发编程基础》的第一部分,Frank Luna已经为我们讲解得非常到位了。

好了,下面就开始依次讲解吧。

Ⅰ.四大变换之一:世界变换

根据物体模型的大小、方向以及其他模型之间的相对关系,世界变换将物体模型从自身的局部坐标系中转换到时间坐标系中,并将所有的物体模型组织为一个场景,如下图:

就像这个世界上有很多不同的个体,我们每个个体都有自己独特的局部坐标系,而我们每个个体仅仅是这个世界中的一份子而已。就像那句英文名言的前半句一样,to the world,you maybe one person。

世界变换包括平移、旋转和缩放变换,我们可以通过D3DX库中D3DXMatrixTranslation、D3DXMatrixRotation*和D3DXMatrixSaling函数来进行变换,并得到一个世界变换矩阵。

其中D3DXMatrixTranslation用于矩阵的平移操作,D3DXMatrixRotation*用于矩阵的旋转操作,D3DXMatrixSaling用于矩阵的缩放操作(稍后会分别介绍)。这三种变换是矩阵的最常用的变换。

调用这些函数将我们矩阵调整好之后,接着我们就调用IDirect3DDevice9接口的SetTransform方法来运用世界变换矩阵,表示认定某某矩阵就是我们的世界变换矩阵了。

我们可以在DirectX SDK中查到SetTransform方法的原型如下:

HRESULT SetTransform(
  [in]  D3DTRANSFORMSTATETYPE State,
  [in]  const D3DMATRIX *pMatrix
);

■ 第一个参数,D3DTRANSFORMSTATETYPE类型的State,明显的可以看到,它是一个D3DTRANSFORMSTATETYPE枚举类型,用于表示变换的类型,是四大变换前三者之中的哪一种。可以取值为D3DTS_WORLD,表示世界矩阵,或者是D3DTRANSFORMSTATETYPE枚举中的一种,这个D3DTRANSFORMSTATETYPE枚举类型定义如下:

typedef enum D3DTRANSFORMSTATETYPE {
  D3DTS_VIEW          = 2,
  D3DTS_PROJECTION    = 3,
  D3DTS_TEXTURE0      = 16,
  D3DTS_TEXTURE1      = 17,
  D3DTS_TEXTURE2      = 18,
  D3DTS_TEXTURE3      = 19,
  D3DTS_TEXTURE4      = 20,
  D3DTS_TEXTURE5      = 21,
  D3DTS_TEXTURE6      = 22,
  D3DTS_TEXTURE7      = 23,
  D3DTS_FORCE_DWORD   = 0x7fffffff 
} D3DTRANSFORMSTATETYPE, *LPD3DTRANSFORMSTATETYPE;

其中D3DTS_VIEW表示取景变换,D3DTS_PROJECTION表示投影变换。而D3DTS_TEXTURE0~7显然表示的就是某层纹理(0到7层)对应的变换矩阵了 。

第二个参数,const D3DMATRIX类型的*pMatrix,是一个实实在在有内容的矩阵,显然就是我们想要设置为和第一个参数中指定的类型相挂钩的候选人矩阵了。

接着我们来分别介绍经常会用到的矩阵的一些基本变换方法:

1.矩阵的平移

首先介绍D3DXMatrixTranslation方法,D3D中的平移函数。

在Direct3D里,平移矩阵应是使用最多的矩阵。这是因为每个物体的相对位置,都是通过平移矩阵来创造出来的。物体的整体移动,说白了就是坐标点的移动。比如从点(x,y,z)移动到新的位置(x', y', z')。在Direct3D就为我们提供了D3DXMatrixTranslation方法用于矩阵的平移。我们先来看一下这个函数的原型:

D3DXMATRIX * D3DXMatrixTranslation(
  __inout  D3DXMATRIX *pOut,  
  __in     FLOAT x,
  __in     FLOAT y,
  __in     FLOAT z
);


这个函数其实就是在创造一个有相对于原点(0,0,0)有偏移量的矩阵出来。我们先来看一下各个参数的意义。

█ 第一个参数,D3DXMATRIX类型的*pOut,从类型上来看我们就知道他是一个D3DXMATRIX类型的4 X 4的矩阵,我们调用这个D3DXMatrixTranslation方法,其实就是在为这个矩阵赋值,让这个矩阵相对于原点有一个偏移量,也就是我们需要平移的距离。

而具体的平移操作其实并不是由这个函数完成的。,这个函数其实是在创建一个平移矩阵。

█ 第二个参数,FLOAT类型的x,显然是X轴的平移量。

█ 第三个参数,FLOAT类型的y,显然是Y轴的平移量。

█ 第四个参数,FLOAT类型的z,显然是Z轴的平移量。

比如要沿着Z轴的正方向平移10个单位,就需要按下面来设置平移矩阵:

D3DXMATRIX mTrans;
D3DXMatrixTranslation(&mTrans,0,0,10);


然后我们把需要进行平移操作的矩阵,乘以这个创建好mTrans矩阵,就完成了平移操作。比如说我们有一个mMtrix矩阵,我们需要这个mMtrix矩阵沿Z轴正方向平移10个单位,就让mTrans和mMtrix相乘就可以了,他们相乘的结果就是mMtrix矩阵向上平移10个单位后的矩阵。其中相乘操作用 D3DXMatrixMultiply来完成。

D3DXMATRIX * D3DXMatrixMultiply(
  __inout  D3DXMATRIX *pOut,
  __in     const D3DXMATRIX *pM1,
  __in     const D3DXMATRIX *pM2
);

第一个参数,为输出的结果。第二和第三个参数为参加乘法的两个矩阵,且pM1在左,pM2在右,因为矩阵相乘涉及到相乘顺序的问题。这个函数的作用可以用一个式子来表示,也就是pM1 * pM2=pOut。

所以,整体来看,要把mMtrix矩阵向Z轴正方向平移10个单位,就是如下的代码:

D3DXMATRIX mTrans;
D3DXMatrixTranslation(&mTrans,0,0,10);
D3DXMatrixMultiply(&mMtrix,&mMtrix,&&mTrans);


再举一个例子,如果我们仅仅是将一个物体沿X轴正方向平移5个单位,Y轴负方向平移3个单位就可以了,也就是只做了平移操作,就可以直接把我们创建的这个中间矩阵作为世界矩阵使用,也就是如下代码:

D3DXMATRIX mTrans;
D3DXMatrixTranslation(&mTrans,5,-3,0);
g_pd3dDevice->SetTransform(D3DTS_WORLD,&mTrans);


2.矩阵的旋转

旋转和平移类似,也是先用一个函数创建好用于旋转的一个中间矩阵,然后让我们需要旋转的那个矩阵右乘这个中间矩阵就好了。关于这个旋转中间矩阵的创建,用的就是地d9

dx9.lib库中的D3DXMatrixRotationX, D3DXMatrixRotationY以及D3DXMatrixRotationZ。这三个函数非常相似,我们就打包起来介绍得了,这三个函数的原型声明如下:

D3DXMATRIX * D3DXMatrixRotationX(
  __inout  D3DXMATRIX *pOut,
  __in     FLOAT Angle
);
D3DXMATRIX * D3DXMatrixRotationY(
  __inout  D3DXMATRIX *pOut,
  __in     FLOAT Angle
);
D3DXMATRIX * D3DXMatrixRotationZ(
  __inout  D3DXMATRIX *pOut,
  __in     FLOAT Angle
);


█ 第一个参数,D3DXMATRIX类型的*pOut,依然是作为输出结果的旋转矩阵。

█ 第二个参数,FLOAT 类型的angle表示要旋转的弧度值。

下面继续来举例子,同样,如果我们只对某个物体在世界坐标系中进行旋转操作的话,也可以把D3DXMatrixRotationX系列函数创造出来的中间矩阵作为我们的世界矩阵。

若要将一个对象沿Y轴旋转90度,也就是如下代码:

D3DXMATRIX mTrans;
float fAngle=90*(2.0f*D3DX_PI)/360.0f;
D3DXMatrixRotationY(&mTrans, fAngle);
g_pd3dDevice->SetTransform(D3DTS_WORLD,&mTrans);


3.矩阵的缩放

与旋转和平移类似,矩阵缩放也是先用一个函数创建好用于缩放的一个中间矩阵,然后让我们需要缩放那个矩阵右乘这个中间矩阵就好了。关于这个缩放中间矩阵的创建,为D3DXMatrixScaling函数,这个函数的原型如下:

D3DXMATRIX * D3DXMatrixScaling(
  __inout  D3DXMATRIX *pOut,
  __in     FLOAT sx,
  __in     FLOAT sy,
  __in     FLOAT sz
);


█ 第一个参数,D3DXMATRIX类型的*pOut,依然是作为输出结果的缩放矩阵。

█ 第二个参数到第四个参数,显然就是浮点型的X,Y,Z轴上的缩放比例了。

比如,要将一个物体在Z轴上放大5倍,代码就是这样写:

D3DXMATRIX mTrans;
D3DXMatrixScaling(&mTrans,1.0f,1.0f,5.0f);
g_pd3dDevice->SetTransform(D3DTS_WORLD,&mTrans);


这一小节的最后,最后我们来一个综合的调用实例,比如要将一个物体在X轴上放大3倍,然后又绕Y轴旋转120度,最后又沿Z轴平移正方向10个单位,实现代码如下:

D3DXMATRIX matWorld;
D3DXMATRIX matTranslate,matRotation,matScale;
D3DXMatrixScaling( & matScale,3.0f,1.0f,1.0f);
float fAngle=120*(2.0f*D3DX_PI)/360.0f;
D3DXMatrixRotationY(&matRotation, fAngle);
D3DXMatrixMultiply(&matWorld,& matScale,& matRotation);
D3DXMatrixTranslation(&matTranslate,0.0f,0.0f,10.0f);
D3DXMatrixMultiply (&matWorld ,&matWorld,& matTranslate);
g_pd3dDevice->SetTransform(D3DTS_WORLD,&mTrans);


另外提一个单位化矩阵的函数,也就是D3DXMatrixIdentity函数。其用法非常简单,唯一的参数就是单位化之后的输出矩阵,也就是这样写:

D3DXMATRIX matWorld;

D3DXMatrixIdentity(&matWorld); // 单位化世界矩阵

另外再提一点,关于矩阵的乘法,因为Direct3D对D3DXMATRIX矩阵类型进行了扩展,对矩阵乘法进行了重载,所以矩阵的乘法运算可以不用拘泥于上面讲到的D3DXMatrixMultiply()来实现了,可以简单地使用乘法运算符“*”就可以了,即多个矩阵的乘积可以这样写:

matAnswer=mat1*mat2*mat3*mat4……*matN

Ⅱ.四大变换之二:取景变换

取景变换,人如其名,就是用来取景的,也就是设置Direct3D中的虚拟摄像机的位置和观察点。对于处于不同位置的虚拟摄像机和观察点,其观察物体模型的视角方向也有所差异,因此实际看到的物体模型的实际形状也有所不同。正所谓“横看成岭侧成峰”,就像这幅图所传达的观点一样:

为了确定一个虚拟摄像机的位置和观察方向,需要指定虚拟摄像机在世界坐标系中的位置、观察点位置以及正方向。为了能够进行取景变换,首先需要通过D3DX库中的D3DXMatrixLookAtLH函数计算并得到一个取景变换矩阵(或观察矩阵),然后同样调用IDirect3DDevice接口的SetTransform方法应用取景变换。

其中,D3DXMatrixLookAtLH函数的声明如下:

D3DXMATRIX * D3DXMatrixLookAtLH(
  __inout  D3DXMATRIX *pOut,
  __in     const D3DXVECTOR3 *pEye,
  __in     const D3DXVECTOR3 *pAt,
  __in     const D3DXVECTOR3 *pUp
);


然后依然是函数参数的介绍:

█ 第一个参数,D3DXMATRIX类型的*pOut,最终生成的观察矩阵。

█ 第二个参数,const D3DXVECTOR3类型的*pEye,指定虚拟摄像机在世界坐标系中的位置。

█ 第三个参数,const D3DXVECTOR3类型的*pAt,为观察点在世界坐标系中的位置。

█ 第四个参数,const D3DXVECTOR3类型的*pUp,为摄像机的上向量,通常设为(0,1,0)就可以了。

依然是一个使用的例子,代码如下:

//建立并设置观察矩阵
D3DXVECTOR3 vEyePt( 0.0f, 8.0f,-10.0f ); //摄像机的位置
D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );//观察点的位置
D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );//向上的向量
D3DXMATRIX matView;//定义一个世界矩阵
D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );//计算出取景变换矩阵
       g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ); //应用取景变换矩阵


Ⅲ.四大变换之三:投影变换

经过上一步的取景变换之后,物体模型就位于观察坐标系中了,然而为了能够将三维场景显示在我们二维的显示平面上(因为我们的显示屏是二维的),还需要通过投影变换将三维物体投影到二维的平面上,这个过程我们就把它叫做透视投影。下面这幅图中以及显示得很清楚了:

我们可以看到,摄像机的距离和角度唯一确定了落地窗的大小。

就好比一个人站在的一间封闭房间里,房间唯一可以接收到光线的地方是一块高大的落地窗,而房间外面对着落地窗这边的,是一堵无限长无限宽,绵延到世界尽头的白色的墙。人眼透过落地窗看到房子外边三维的世界,最终投影在了那堵二维的墙上。

显然,投影窗口是一个二维平面,用于描述三维物体模型经过透视投影后的二维图像,在Direct3D中投影窗口平面默认定义为z=1的平面。

虚拟摄像机与投影窗口平面共同构成了一个对观察者可见的三维空间。在3D图形学中这部分空间被称作视截体(View Frustum),位于视截体内的物体模型被映射到二维投影投影平面上,而位于视截体外的物体模型或者其中一部分将不可见,这个过程我们称作裁剪(Clipping),(取屏幕内的景物,专注,屏幕外的一律无视)。

投影变换负责将位于视截体内的物体模型映射到投影窗口中。D3DX库中的D3DXMatrixPerspectiveFovLH函数可以用来计算一个视截体,并根据该视截体的描述信息创建一个投影矩阵变换。这个D3DXMatrixPerspectiveFovLH方法可以在MSDN中查到如下函数原型;

D3DXMATRIX * D3DXMatrixPerspectiveFovLH(
  __inout  D3DXMATRIX *pOut,
  __in     FLOAT fovy,
  __in     FLOAT Aspect,
  __in     FLOAT zn,
  __in     FLOAT zf
);

■ 第一个参数,D3DXMATRIX类型的*pOut,它为我们最终生成的投影变换矩阵。

■ 第二个参数,FLOAT类型的fovy,用于指定以弧度为单位的虚拟摄像机在y轴上的成像角度,即视域角度(View of View),成像角度越大,映射到投影窗口中的图形就越小;反之,投影图像就越大。

■ 第三个参数,FLOAT类型的Aspect,用于描述屏幕显示区的横纵比,他的值就为屏幕的宽度/高度。对应不同比例的显示屏幕,比如16/9,4/3等等,最终显示的投影图像可能会使图像被拉伸

■ 第四个参数, FLOAT类型的zn,表示视截体中近裁剪面距我们摄像机的位置,即人眼到“室内落地窗”之间的距离

■ 第五个参数,FLOAT类型的zf,表示视截体中远裁剪面距我们摄像机的位置,即人眼到“室外黑色墙壁”之间的距离

依旧是一个调用实例:

//建立并设置投影矩阵
       D3DXMATRIXA16 matProj;
       float aspect = (float)(800/600);
       D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, aspect, 1.0f, 100.0f );
       g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );


Ⅳ. 四大变换之四:视口变换

终于讲到四大变换的最后一个了。其实这一步非常的简单,也就是填下填空题,然后设置一下就好了。并不像前三大变换一样要用到矩阵相关的知识。下面就一起来具体看看视口变换的相关概念吧。

视口变换用于将投影窗口中的图形转换到显示屏幕的程序窗口中。视口是程序窗口中前的一个矩形区域,他可以是整个程序窗口,也可以是窗口的客户区,也可以是窗口中其他矩形区域,如图所示:

在Direct3D中,视口是由D3DVIEWPROT9结构体来描述的,其中定义了视口的位置,宽度高度等信息,在DirectX SDK 中我们可以查到该结构体的声明如下:

typedef struct D3DVIEWPORT9 {
  DWORD X;          //表示视口相对于窗口的X坐标
  DWORD Y;     //视口相对对窗口的Y坐标
  DWORD Width;//视口的宽度
  DWORD Height;//视口的高度
  float MinZ;//视口在深度缓存中的最小深度值
  float MaxZ;  //视口在深度缓存中的最大深度值
} D3DVIEWPORT9, *LPD3DVIEWPORT9;


讲解完概念下面依旧是调用的实例:

方法一:

先做填空题,做完之后调用IDirect3D9Device9接口的SetViewPort方法设置当前窗口中的视口,然后Direct3D将自动完成视口变换。

D3DVIEWPORT9 vp={0,0,800,600,0,1};
g_pD3dDevice->SetViewport(&vp);

方法二:

结构体的内容别开来赋值:

D3DVIEWPORT9 vp;
         vp.X      = 0;
         vp.Y      = 0;
         vp.Width  = 800;
         vp.Height = 600;
         vp.MinZ   = 0.0f;
         vp.MaxZ   = 1.0f;
         g_pd3dDevice->SetViewport(&vp);

其实也就是填结构体填空题的两种方式而已。

Ⅴ.总结

四大变换分为:世界变换,取景变换,投影变换,视口变换。

1.为了能在世界空间中的指定位置来绘制图形,就需要在绘制图形前进行四大变换之一的世界变换运算。

2.为了以不同的视角观察图形,就需要用到四大变换之二的取景变换运算。

3.为了将相对较远的图形投影到同一个平面上并体现出“近大远小”的真实视觉效果,就需要用到四大变换之三的投影变换。

4.为了控制显示图形的窗口的大小,比例以及深度等等信息,就要用到四大变换之四的视口变换。

三,详细注释的源代码欣赏

这里依旧是通过一个小程序,来把本篇文章所学的知识融会贯通。这篇文章里我们讲解了绘制3D图形时用到的四大变换,下面就是一个利用四大变换来绘制一个转动的颜色随机的彩色立方体的demo。我们将四大变换封装在了一个全局的Matrix_Set()函数中了,并在渲染五步曲之三中进行了Matrix_Set()函数的调用。

在放出完整的源代码之前,让我们首先我们来看一下本篇文章介绍的核心知识,也就是实现了四大变换封装的Matrix_Set()函数的具体实现方法:

//*****************************************************************************************
// Name:Matrix_Set()
// Desc: 设置世界矩阵
// Point:【Direct3D四大变换】
//		1.【四大变换之一】:世界变换矩阵的设置
//		2.【四大变换之二】:取景变换矩阵的设置
//		3.【四大变换之三】:投影变换矩阵的设置
//		4.【四大变换之四】:视口变换的设置
//*****************************************************************************************
VOID Matrix_Set()
{
	//--------------------------------------------------------------------------------------
	//【四大变换之一】:世界变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matWorld, Rx, Ry, Rz;
	D3DXMatrixIdentity(&matWorld);                  // 单位化世界矩阵
	D3DXMatrixRotationX(&Rx, D3DX_PI *(::timeGetTime() / 1000.0f));    // 绕X轴旋转
	D3DXMatrixRotationY(&Ry, D3DX_PI *( ::timeGetTime() / 1000.0f/2));    // 绕Y轴旋转
	D3DXMatrixRotationZ(&Rz, D3DX_PI *( ::timeGetTime() / 1000.0f/3));   // 绕Z轴旋转
	matWorld = Rx * Ry * Rz * matWorld;             // 得到最终的组合矩阵
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);  //设置世界变换矩阵

	//--------------------------------------------------------------------------------------
	//【四大变换之二】:取景变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matView; //定义一个矩阵
	D3DXVECTOR3 vEye(0.0f, 0.0f, -200.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, 1.0f, 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); //视口的设置

}


其中timeGetTime方法以毫秒为时间单位返回Windows系统开机时起所经过的时间。这样就可以通过(::timeGetTime() / 1000.0f)这个式子来构造一个从0到1的连续的时间周期.而D3DX_PI 为Direct3D中定义的一个宏,原型为:

#define D3DX_PI    ((FLOAT)  3.141592654f)


接着我们就贴出这个小程序全部的详细注释的源代码,按键盘上的1键和2键可以在线框填充模式和实体填充模式之间切换:

//*****************************************************************************************
//
//【Visual C++】游戏开发笔记系列配套源码 三十八 浅墨DirectX提高班之六 携手迈向三维世界:四大变换展身手
//		 VS2010版
// 2012年 12月23日  Create by 浅墨 
//图标素材: Dota2 白虎第1技能 流星
//源码配套博文链接:  
//更多内容请访问我的博客: http://blog.csdn.net/zhmxy555 
//此刻心情:耿耿于怀着过去和忐忑不安着未来的人,也常常挥霍无度着现在。希望我们都做那个把握好现在的人。
//
//***************************************************************************************** 




//*****************************************************************************************
// Desc: 头文件定义部分  
//*****************************************************************************************                                                                                       
#include 
#include 
#include 
#include    




//*****************************************************************************************
// Desc: 库文件定义部分  
//***************************************************************************************** 
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib, "winmm.lib ")


//*****************************************************************************************
// Desc: 宏定义部分   
//*****************************************************************************************
#define SCREEN_WIDTH	800						//为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define SCREEN_HEIGHT	600							//为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE	_T("【Visual C++游戏开发笔记】博文配套demo之三十八 浅墨DirectX提高班之六 携手迈向三维世界:四大变换展身手") //为窗口标题定义的宏
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }      //自定义一个SAFE_RELEASE()宏,便于资源的释放



//*****************************************************************************************
// 【顶点缓存、索引缓存绘图四步曲之一】:设计顶点格式
//*****************************************************************************************
struct CUSTOMVERTEX
{
	FLOAT x, y, z;
	DWORD color;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)  //FVF灵活顶点格式


//*****************************************************************************************
// Desc: 全局变量声明部分  
//*****************************************************************************************
LPDIRECT3DDEVICE9       g_pd3dDevice = NULL; //Direct3D设备对象
ID3DXFont*				g_pFont=NULL;    //字体COM接口
float					g_FPS = 0.0f;       //一个浮点型的变量,代表帧速率
wchar_t					g_strFPS[50];    //包含帧速率的字符数组
LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer = NULL;    //顶点缓存对象
LPDIRECT3DINDEXBUFFER9  g_pIndexBuffer  = NULL;    // 索引缓存对象

//*****************************************************************************************
// Desc: 全局函数声明部分 
//***************************************************************************************** 
LRESULT CALLBACK	WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
HRESULT				Direct3D_Init(HWND hwnd);
HRESULT				Objects_Init();
void				Direct3D_Render( 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)))
	{
		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);  //对窗口进行更新,就像我们买了新房子要装修一样

	

	

	//消息循环过程
	MSG msg = { 0 };  //初始化msg
	while( msg.message != WM_QUIT )			//使用while循环
	{
		if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )   //查看应用程序消息队列,有消息时将队列中的消息派发出去。
		{
			TranslateMessage( &msg );		//将虚拟键消息转换为字符消息
			DispatchMessage( &msg );		//该函数分发一个消息给窗口程序。
		}
		else
		{
			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)
{

	//--------------------------------------------------------------------------------------
	// 【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;


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



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

	return S_OK;
}


HRESULT Objects_Init()
{
	//创建字体
	if(FAILED(D3DXCreateFont(g_pd3dDevice, 30, 0, 0, 1, FALSE, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("宋体"), &g_pFont)))
		return E_FAIL;


	srand((unsigned)time(NULL));      //初始化时间种子


	//--------------------------------------------------------------------------------------
	// 【顶点缓存、索引缓存绘图四步曲之二】:创建顶点缓存和索引缓存
	//--------------------------------------------------------------------------------------
		//创建顶点缓存
		if( FAILED( g_pd3dDevice->CreateVertexBuffer( 8*sizeof(CUSTOMVERTEX),
			0, D3DFVF_CUSTOMVERTEX,
			D3DPOOL_DEFAULT, &g_pVertexBuffer, NULL ) ) )
		{
			return E_FAIL;
		}
		// 创建索引缓存
	if( FAILED( 	g_pd3dDevice->CreateIndexBuffer(36* sizeof(WORD), 0, 
		D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_pIndexBuffer, NULL)) )
		{
		return E_FAIL;

		}
		//--------------------------------------------------------------------------------------
		// 【顶点缓存、索引缓存绘图四步曲之三】:访问顶点缓存和索引缓存
		//--------------------------------------------------------------------------------------
		//顶点数据的设置,
	//顶点数据
	CUSTOMVERTEX Vertices[] =
	{
		{ -20.0f, 20.0f, -20.0f,  D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256) },
		{ -20.0f, 20.0f, 20.0f,  D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256) }, 
		{ 20.0f, 20.0f, 20.0f,  D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256) },
		{ 20.0f, 20.0f, -20.0f,  D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256) },
		{ -20.0f, -20.0f, -20.0f, D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256) },
		{ -20.0f, -20.0f, 20.0f, D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256) }, 
		{ 20.0f, -20.0f, 20.0f, D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256) },
		{ 20.0f, -20.0f, -20.0f, D3DCOLOR_XRGB(rand() % 256, rand() % 256, rand() % 256) },

	};

		//填充顶点缓存
		VOID* pVertices;
		if( FAILED( g_pVertexBuffer->Lock( 0, sizeof(Vertices), (void**)&pVertices, 0 ) ) )
			return E_FAIL;
		memcpy( pVertices, Vertices, sizeof(Vertices) );
		g_pVertexBuffer->Unlock();



		// 填充索引数据
		WORD *pIndices = NULL;
		g_pIndexBuffer->Lock(0, 0, (void**)&pIndices, 0);
		// 顶面
		pIndices[0] = 0, pIndices[1] = 1, pIndices[2] = 2;
		pIndices[3] = 0, pIndices[4] = 2, pIndices[5] = 3;

		// 正面
		pIndices[6] = 0, pIndices[7]  = 3, pIndices[8]  = 7;
		pIndices[9] = 0, pIndices[10] = 7, pIndices[11] = 4;

		// 左侧面
		pIndices[12] = 0, pIndices[13] = 4, pIndices[14] = 5;
		pIndices[15] = 0, pIndices[16] = 5, pIndices[17] = 1;

		// 右侧面
		pIndices[18] = 2, pIndices[19] = 6, pIndices[20] = 7;
		pIndices[21] = 2, pIndices[22] = 7, pIndices[23] = 3;

		// 背面
		pIndices[24] = 2, pIndices[25] = 5, pIndices[26] = 6;
		pIndices[27] = 2, pIndices[28] = 1, pIndices[29] = 5;

		// 底面
		pIndices[30] = 4, pIndices[31] = 6, pIndices[32] = 5;
		pIndices[33] = 4, pIndices[34] = 7, pIndices[35] = 6;
		g_pIndexBuffer->Unlock();


	return S_OK;
}


//*****************************************************************************************
// Name:Matrix_Set()
// Desc: 设置世界矩阵
// Point:【Direct3D四大变换】
//		1.【四大变换之一】:世界变换矩阵的设置
//		2.【四大变换之二】:取景变换矩阵的设置
//		3.【四大变换之三】:投影变换矩阵的设置
//		4.【四大变换之四】:视口变换的设置
//*****************************************************************************************
VOID Matrix_Set()
{
	//--------------------------------------------------------------------------------------
	//【四大变换之一】:世界变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matWorld, Rx, Ry, Rz;
	D3DXMatrixIdentity(&matWorld);                  // 单位化世界矩阵
	D3DXMatrixRotationX(&Rx, D3DX_PI *(::timeGetTime() / 1000.0f));    // 绕X轴旋转
	D3DXMatrixRotationY(&Ry, D3DX_PI *( ::timeGetTime() / 1000.0f/2));    // 绕Y轴旋转
	D3DXMatrixRotationZ(&Rz, D3DX_PI *( ::timeGetTime() / 1000.0f/3));   // 绕Z轴旋转
	matWorld = Rx * Ry * Rz * matWorld;             // 得到最终的组合矩阵
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);  //设置世界变换矩阵

	//--------------------------------------------------------------------------------------
	//【四大变换之二】:取景变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matView; //定义一个矩阵
	D3DXVECTOR3 vEye(0.0f, 0.0f, -200.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, 1.0f, 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); //视口的设置

}



//*****************************************************************************************
// 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(0, 0, 0), 1.0f, 0);

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

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

	Matrix_Set();

	// 获取键盘消息并给予设置相应的填充模式
	if (::GetAsyncKeyState(0x31) & 0x8000f)         // 若数字键1被按下,进行线框填充
		g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
	if (::GetAsyncKeyState(0x32) & 0x8000f)         // 若数字键2被按下,进行实体填充
		g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之三】:正式绘制,利用顶点缓存绘制图形
	//--------------------------------------------------------------------------------------

	//--------------------------------------------------------------------------------------
	// 【顶点缓存、索引缓存绘图四步曲之四】:绘制图形
	//--------------------------------------------------------------------------------------
	// 设置渲染状态
	g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
	g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);   //开启背面消隐

	g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(CUSTOMVERTEX) );//把包含的几何体信息的顶点缓存和渲染流水线相关联
	g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );//指定我们使用的灵活顶点格式的宏名称
	g_pd3dDevice->SetIndices(g_pIndexBuffer);//设置索引缓存
	g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 8, 0, 12);//利用索引缓存配合顶点缓存绘制图形


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

	//--------------------------------------------------------------------------------------
	// 【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接口对象
	SAFE_RELEASE(g_pIndexBuffer)
	SAFE_RELEASE(g_pVertexBuffer)
	SAFE_RELEASE(g_pFont)
	SAFE_RELEASE(g_pd3dDevice)	
}


多次运行这个demo,我们看到颜色随机的绚烂的立方体在三维空间中以一定的节奏转动着,按下键盘上的1键,我们会看到立方体的线框轮廓,而默认情况下或者按下键盘上的2键,我们会看到立方体的实体轮廓。

浅墨相信通过之前两篇文章的讲解,理解起这个立方体的创建过程并不是什么难事。

在demo之中,按键更改填充模式的代码实现如下,也就是在渲染五步曲第三步中加入如下代码就可以了:

// 获取键盘消息并给予设置相应的填充模式

         if (::GetAsyncKeyState(0x31) & 0x8000f)         // 若数字键1被按下,进行线框填充

                   g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);

         if (::GetAsyncKeyState(0x32) & 0x8000f)         // 若数字键2被按下,进行实体填充

                   g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);

其中,GetAsyncKeyState函数用于获取当前键盘上按键的按下状态。关于渲染状态,后面我们会讲解到,这里暂时就给大家一个印象吧。

最后们我们放出这个demo的运行截图:

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

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

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

其中图标素材使用的是Dota2中的白虎的技能“流星“

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

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

另外,浅墨有幸成为了2012年CSDN年度博客之星候选人之一。
在这里,恳请支持浅墨,喜欢游戏开发系列文章的朋友们去
投浅墨一票,有了大家的支持,浅墨会更用心地写出更优秀的博客文章来与大家分享,把技术分享这种信仰传递下去。

大家的支持就是浅墨继续写下去的动力~~~(有朋友说想给我投票却不好找链接,其实链接浅墨已经给出了- -,点击上面红色的“投浅墨一票”字样即可跳到浅墨的参展页面)

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

当你可以直面自己身体里与生俱来的笨拙与孤独,你便能够彻底谅解过去的自己。大多数人都像我们这样活着,虽不聪明,但诚恳;虽会犯错,但坦然。

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

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

你可能感兴趣的:(【Visual C++】游戏开发笔记三十八 浅墨DirectX提高班之六 携手迈向三维世界:四大变换展身手...)