第13章 迈向三维世界——Direct3D 的四大变换

本章我们将介绍使用Direct3D 创建三维游戏的基础,四大变换的概念和使用的方方面面。所谓四大变换,其实是作者结合自己的经验,总结出来的对世界变换、取景变换、投影变换和视口变换的总称而已。

13.1 四大变换的基本认知

首先,我们来讲讲周边的概念。
在Direct3D 中,如果我们未进行任何空间坐标变换而直接绘制图形的话,图形将始终处于应 用程序窗口的中心位置,在默认情况下这个位置就会成为世界坐标系的原点(0,0,0 ) 。另外,我们 也不能改变观察图形的视角方向。默认情况下的观察方向是世界坐标系的Z 轴正方向,也就是垂 直程序窗口并向里的方向。之前我们的几个demo 都是默认的Z 轴正方向为观察方向的。
为了迈向三维世界,为了能够随心所欲地在三维世界中绘制和观察游戏画面,我们需要了解四 大变换的相关知识。
所谓四大变换,其实是作者自己在学习与运用Direct3D 的时候归纳出的,也就是世界变换、 取景变换、投影变换和视口变换的总称。
下面我们先来大致概括一下这四大变换的用途。
  • 为了能在世界空间中的指定位置绘制图形,就需要在绘制图形前进行四大变换之一的世界变换运算。
  • 为了以不同的视角观察图形,就需要用到四大变换之二的取景变换运算。
  • 为了将相对较远的图形投影到同一个平面上并体现出“近大远小”的真实视觉效果,就需要用到四大变换之二的投影变换。
  • 为了控制显示图形的窗口的大小、比例以及深度等信息,就要用到四大变换之四的视口变换;
下面我们分别展开讲解这四大变换的细节知识。在讲解之前,有必要先跟大家脱明一下。这里 的四大变换的前三个变换都是以矩阵作为载体的。这四大变换相关知识中或多或少会涉及到矩阵、 向量、平面和射线等相关的数学知识。
好了,下面就开始依次讲解。

13.2 四大变换之一:世界变换

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

第13章 迈向三维世界——Direct3D 的四大变换_第1张图片

就像这个世界上有很多不同的个体,每个个体都有自己独特的局部坐标系, 而每个个体又仅仅是这个世界中的一份子而己。
世界变换包括平移、旋转和缩放变换,我们可以通过D3DX 库中D3DXMatrixTranslation 、D3DXMatrixRotation*和D3DXMatrixScaling 函数来进行变换, 并得到一个世界变换矩阵。
其中D3DXMatrixTranslation 用于矩阵的平移操作, D3DXMatrixRotation* 用于矩阵的旋转操作,D3DXMatrixScaling 用于矩阵的缩放操作(稍后会分别介绍〉。这3 种变换是矩阵的最常用的变换。
调用这些函数将我们矩阵调整好之后, 接着我们就调用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 ,是一个有实实在在内容的矩阵,显然就是我们想要设置为与第一个参数中指定的类型相挂钩的候选人矩阵了。
接着我们分别来介绍经常会用到的矩阵的一些基本变换方法。

13.2.1 矩阵的平移

首先介绍D3DXMatrixTranslation 方法, D3D 中的平移函数。
在Direct3D里,平移矩阵应是使用最多的矩阵。这是因为每个物体的相对位置,都是通过平移矩阵来创造出来的。物体的整体移动,说白了就是坐标点的移动。比如从点(x、y、z)移动到新的位置(x’, y’, z’)。Direot3D 为我们提供了

D3DXMatrixTranslation 方法用于矩阵的平移。我们先来看一下这个函数的原型:

 D3DXMATRIX * D3DXMatrixTranslation(
  __inout  D3DXMATRIX *pOut,
  __in     FLOAT x,
  __in     FLOAT y,
  __in     FLOAT z
);
这个函数其实用于创造一个相对于原点(0, 0, 0)有偏移量的矩阵出来。我们先来看一下各个参数的意义。

  • 第一个参数, D3DXMATRIX 类型的*pOut ,从类型上来看就知道它是一个D3DXMATRIX类型的4 × 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 = 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) ;
另外, 关于矩阵的乘法,因为Direct3D对D3DXMATRIX 矩阵类型进行了扩展,对矩阵乘法进行了重载,所以矩阵的乘法运算可以不用拘泥于使用上面讲到的D3DXMatrixMultiply()来实现了,可以简单地使用乘法运算符“ * ”就可以了,即多个矩阵的乘积可以这样写:

 matAnswer=matl*mat2*mat3*mat 4…··· * matN

13.2.2 矩阵的旋转

旋转和平移类似,也是先用一个函数创建好用于旋转的一个中间矩阵,然后让我们需要旋转的那个矩阵右乘这个中间矩阵就好了。关于这个旋转中间矩阵的创建,用的就是d9dx9.lib 库中的D3DXMatrixRotationX、D3DXMatrixRotationY 以及D3DXMatrixRotationZ 。这3 个函数非常相似,我们可以一走介绍,这3 个函数的原型声明如下:

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

13.2.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 ;
 D3DMatrixScaling (&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 . Of , 1. 时, 1. Of) ; // 定义出一个X 轴3 倍长的单位矩阵
 float fAngle=l20*(2.0f*D3DX_PI) /360.0f ; // 定义出一个120 度的角
 D3DXMatrixRotationY(&matRotation , fAngle) ; // 得到一个绕Y 轴旋转60度的角
 D3DXMatrixMultiply (&matWorld, & matScale, & matRotation) ; // matWorld=matScale*matRotation
 D3DXMatrixTranslation(&matTranslate,O.Of,O.Of,10.0f); // 定义出一个沿Z 轴平移10个单位的矩阵
 D3DXMatrixMultiply (&matWorld ,&matWorld,& matTranslate); // matWorld=matWorld *matTranslate
 g_pd3dDevice->SetTransform(D3DTS_WORLD, &mTrans); // 让计算出来的世界矩阵运用到Direct3D 程序中
对于缩放、旋转和平移都上场的情况,记住一个原则——先缩放、再旋转、最后平移。

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

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

13.3 四大变换之二:取景变换

取景变换,人如其名,就是用来取景的,也就是设置Direct3D 中的虚拟摄像机的位置和观察点。

对于处于不同位置的虚拟摄像机和观察点,其观察物体模型的视角方向也有所差异,因此实际看到的物体模型的实际形状也有所不同。正所谓“横看成岭侧成峰”,就像这幅图所表达的观点一样:

第13章 迈向三维世界——Direct3D 的四大变换_第2张图片

为了确定一个虚拟摄像机的位置和观察方向,需要指定虚拟摄像机在世界坐标系中的位置、观察点位置以及正方向。为了能够进行取景变换,首先需要通过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 D3DXVECTO阳类型的*pUp , 为摄像机的上向量,通常设为( 0, 1 ,0 )就可以了。
依然是一个使用的例子,代码如下:

//【四大变换之二】:取景变换矩阵的设置
	//--------------------------------------------------------------------------------------
	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); //应用取景变换矩阵

13.4 四大变换之三:投影变换

经过上一步的取景变换之后,物体模型就位于观察坐标系中了。然而,为了能够将三维场景显示在二维的显示平面上(显示屏是二维的),还需要通过投影变换将三维物体投影到二维的平面上,这个过程我们把它叫做透视投影或投影变换。
下面这幅图就显示得很清楚了。
第13章 迈向三维世界——Direct3D 的四大变换_第3张图片
可以看到, 动漫角色眼睛处(即摄像机处)规定的距离和角度唯一确定了投影窗口的大小。.就好比一个人站在一间封闭房间里,房间唯一可以接收到光线的地方是一块高大的落地窗,而房间外面对着落地窗这边的是一堵无限长无限宽,绵延到世界尽头的白色的墙。人眼透过落地窗看到房子外边三维的世界, 最终投影在了那堵二维的墙上。
显然,投影窗口是一个二维平面,用于描述三维物体模型经过透视投影后的二维图像,在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,表示视截体中远裁剪面距我们摄像机的位置, 即人眼到“室外黑色墙壁”之间的距离。

依旧是一个调用实例:

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

13.5 四大变换之四:视口变换

 终于讲到四大变换的最后一个了。其实这一步非常简单,也就是填下填空题, 然后设置一 下就好了。并不像前三大变换一样要用到矩阵的相关知识。下面具体看看视口变换的相关概念吧。
视口变换用于将投影窗口中的图形转换到显示屏幕的程序窗口中。视口是程序窗口中的一个 矩形区域,它可以是整个程序窗口, 可以是窗口的客户区,也可以是窗口中其他矩形区域,如下图 所示。

第13章 迈向三维世界——Direct3D 的四大变换_第4张图片

在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; //实例化一个D3DVIEWPORT9结构体,然后做填空题给各个参数赋值就可以了
	vp.X      = 0;		//表示视口相对于窗口的X坐标
	vp.Y      = 0;		//视口相对对窗口的Y坐标
	vp.Width  = WINDOW_WIDTH;	//视口的宽度
	vp.Height = WINDOW_HEIGHT; //视口的高度
	vp.MinZ   = 0.0f; //视口在深度缓存中的最小深度值
	vp.MaxZ   = 1.0f;	//视口在深度缓存中的最大深度值
	g_pd3dDevice->SetViewport(&vp); //视口的设置
这两种方法其实也就是结构体填空题的两种方式而己,窗口类WNDCLASS 的定义也是这样的,可以触类旁通。

13.6 总结

我们的四大变换为: 世界变换,取景变换,投影变换,视口变换。
  • 为了能在世界空间中的指定位位置绘制图形,就需要在绘制图形前进行四大变换之一的世界变换运算。
  • 为了以不同的视角观察图形, 就需要用到四大变换之二的取景变换运算。
  • 为了将相对较远的图形投影到同一个平面上并体现出“近大远小” 的真实视觉效果,就需要用到四大变换之三的投影变换。
  • 为了控制显示图形的窗口的大小,比例以及深度等等信息,就要用到四大变换之四的视口变换。

13.7 示例程序D3Ddemo5

上一节讲解了绘制3D 图形时用到的四大变换, 本节将利用四大变换来绘制一个转动的颜色随机的彩色立方体的demo 。我们把四大变换封装在了一个全局的Matrix_Set()函数中,并在渲染五步曲的第三步中进行了Matrix_Set() 函数的调用。
在放出实现的核心源代码之前,我们首先来看一下本节介绍的核心知识,也就是实现了四大变换封装的Matrix_Set()函数的具体实现方法:

//-----------------------------------【Matrix_Set( )函数】--------------------------------------
//	描述:封装了Direct3D四大变换的函数,即世界变换,取景变换,投影变换,视口变换的设置
//--------------------------------------------------------------------------------------------------
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  = WINDOW_WIDTH;	//视口的宽度
	vp.Height = WINDOW_HEIGHT; //视口的高度
	vp.MinZ   = 0.0f; //视口在深度缓存中的最小深度值
	vp.MaxZ   = 1.0f;	//视口在深度缓存中的最大深度值
	g_pd3dDevice->SetViewport(&vp); //视口的设置
}
其中timeGetTime 方法以毫秒为时间单位返回从Windows 系统开机时起所经过的时间。这样就可以通过(::timeGetTime() / 1000.0)这个式子来构造一个从0 到1的连续的时间周期。D3DX_PI 是Direct3D中定义的一个宏,原型为:
#define D3DX_PI    ((FLOAT)  3.141592654f)
接着我们就贴出本节示例程序的核心源代码,它现实的功能是按键盘上的1 键和2 键可以在线框填充模式和实体填充模式之间切换。
//------------------------------------------------------------------------------------------------
// 【顶点缓存使用四步曲之一】:设计顶点格式
//------------------------------------------------------------------------------------------------
struct CUSTOMVERTEX
{
	FLOAT x, y, z;
	DWORD color;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)  //FVF灵活顶点格式

//-----------------------------------【全局变量声明部分】-------------------------------------
//	描述:全局变量的声明
//------------------------------------------------------------------------------------------------
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;    // 索引缓存对象

//-----------------------------------【Object_Init( )函数】--------------------------------------
//	描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化
//--------------------------------------------------------------------------------------------------
HRESULT Objects_Init(HWND hwnd)
{
	//创建字体
	if(FAILED(D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1, false, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("微软雅黑"), &g_pFont)))
		return E_FAIL;
	srand(timeGetTime());      //用系统时间初始化随机种子 

	//--------------------------------------------------------------------------------------
	// 【顶点缓存、索引缓存绘图四步曲之二】:创建顶点缓存和索引缓存
	//--------------------------------------------------------------------------------------
	//创建顶点缓存
	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;
}

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

	//定义一个矩形,用于获取主窗口矩形
	RECT formatRect;
	GetClientRect(hwnd, &formatRect);
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之二】:开始绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->BeginScene();                     // 开始绘制

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

		Matrix_Set();//调用封装了四大变换的函数,对Direct3D世界变换,取景变换,投影变换,视口变换进行设置
	// 获取键盘消息并给予设置相应的填充模式
	if (::GetAsyncKeyState(0x31) & 0x8000f)         // 若数字键1被按下,进行线框填充
		g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
	if (::GetAsyncKeyState(0x32) & 0x8000f)         // 若数字键2被按下,进行实体填充
		g_pd3dDevice->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);

	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,39,136));

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

多次运行这个程序, 我们会得到如下的、旋转着的随机颜色的立方体来,并且可以通过按键盘上的1 键和2 键在线框填充模式和实体填充模式之间切换。
第13章 迈向三维世界——Direct3D 的四大变换_第5张图片
第13章 迈向三维世界——Direct3D 的四大变换_第6张图片


13.8 章节小憩

通过这一章的学习, 我们己经从二维的平面世界转到了三维的立体世界。按这样的进度,离用Direcr3D 绘制出绚丽游戏场景还会远吗?











你可能感兴趣的:(Windows游戏开发)