游戏图形技术入门——掌握DirectX编程(下)
技术与开发
编者按:上期,我们向大家介绍了游戏图形编程的一些基础知识,并教大家学会了利用DirectX进行基本的2D图形编程。在有了2D图形编程的基础之后,本期,我们就将大家带入DirectX的3D游戏图形设计世界。
上一期我们介绍了DirectX和游戏开发中的2D知识。现在,3D网络游戏已成为主流,暴雪的《魔兽世界》获得了巨大的成功,赢得了千千万万的玩家支持;网易的大型3D网络游戏《天下二》自2007年3月份进行公测以来,也取得不错的成绩。这一期我们将详细介绍3D游戏图形开发的相关知识。
一、了解Direct3D的坐标系
3D坐标系有X、Y、Z三个坐标轴。以Direct3D使用的左手坐标系为例,假设你站在3D坐标系的原点上,你正向着Z轴的正方向望去,Y轴的正方向在你正上方,X轴的正方向在你的右手边,图1所示的就是Direct3D使用的左手坐标系。

在3D世界里,游戏里的人物角色、房子、树木等等都是由三角形所组成的。如一个四边形可以由两个三角形组成。用三角形组成一个复杂的游戏人物角色,也许你觉得这不可思议,如何才能把一大堆三角形富有艺术感地组成一个人物呢?如果你是游戏程序员,那你大可不必费心。一些3D建模软件(如3D Studio Max)专门用于完成3D建模工作,游戏美工们也会很乐意去做这项工作。
想象一个简单的3D场景:一块平地,上面一间小房子,房子旁边一棵小树。要用Direct3D表现出这样一个3D场景,首先要把平地、小房子和小树的三角形网格准备好,然后把它们放置到合适的坐标上,设置好投影参数,把摄像机对准它们,打好灯光,最后便可以把这个3D场景渲染出来了。
二、驾驭Direct3D
1.初始化Direct3D
在使用Direct3D前必须先进行初始化,初始化的工作都是千篇一律的,而且这段程序写一次便可以供以后多次使用,这里我就不浪费篇幅在Direct3D的初始化方法介绍了。各位读者可以在《Introduction to 3D Game programming with DirectX 9.0》上找到详细的介绍。
我们假设Direct3D的初始化工作已成功完成,假设pDevice为Direct3D设备对象,定义如下。
IDirect3DDevice9* pDevice;
2.“画”出第一个三角形
既然复杂的3D图形都是由三角形构成的,那么,首先我们必须学会“画”出自己的第一个三角形(如图2所示)。

一个三角形是由三个顶点组成的,在Direct3D里,一个顶点可以包含许多属性,而不只是简单的三维坐标值,还可以包含颜色、法线向量等等。Direct3D让程序员可以灵活地自定义一个顶点的结构,以下代码定义了一个顶点结构。
struct Vertex
{ float x, y, z; // 三维坐标值
DWORD dwColor; // 颜色值 };
另外,我们需要告诉Direct3D我们的顶点是由哪些属性组成的,如下:
#define FVF_VERTEX ( D3DFVF_XYZ | D3DFVF_DIFFUSE )
上面的结构说明我们自定义的顶点包含三维坐标值与颜色值。要注意的是,以上标志的顺序必须和顶点结构里的属性顺序一一对应。
现在有了顶点结构,要绘制一个三角形需要把三角形三个顶点的数据填进到顶点缓冲区里。这里提到的顶点缓冲区是一块连续的存储了顶点数据的内存。在创建顶点缓冲区时需要给出所有顶点的总大小,顶点的格式(即上面的FVF_VERTEX)等参数。创建顶点缓冲区的代码这里就不介绍了,我们假设三角形的顶点缓冲区vbTriangle已成功创建,并把三角形的三个顶点填进里面了。
Vertex vertex[3];
vertex[0] = Vertex( -1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB( 255, 0, 0 ) );
vertex[1] = Vertex( 0.0f, 1.0f, 2.0f, D3DCOLOR_XRGB( 0,255, 0 ) );
vertex[2] = Vertex( 1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB( 0, 0, 255 ) );
IDirect3DVertexBuffer9* vbTriangle;
// 创建顶点缓冲区
// 把三个顶点的数据填进顶点缓冲区
3.摄像机与投影
3D游戏看上去像是在拍电影,在玩《魔兽世界》时,你是不是感觉像是你拿着个摄像机在后面追着你的英雄跑呢?我们所创造的世界可以是很大的,要把上面那三角形显示在屏幕上须要把摄像机对准它。设置摄像机需要三个向量:摄像机所在的位置、摄像机所对准的目标、3D世界中的上方向。以下的代码的作用是在3D世界里放置一个摄像机。
D3DXVECTOR3 pos( 0.0f, 3.0f, -10.0f );
D3DXVECTOR3 target( 0.0f, 0.0f, 0.0f );
D3DXVECTOR3 up( 0.0f, 1.0f, 0.0f );
D3DXMATRIX matView;
D3DXMatrixLookAtH( &matView, &pos, &target, &up );
pDevice->SetTransform( D3DTS_VIEW, &matView );
我们的场景是3D的,而显示在显示器平面上的是2D图像,这种把3D场景转化为2D图像的过程称之为投影。这就好像是用摄像机所拍摄的影片在屏幕播放。在进行投影时须要设置一些参数,如屏幕的宽度与高度的比例值等等。以下代码为将画面投影到640×480分辨率屏幕的例子。
int nWidth = 640; // 屏幕宽度
int nHeight = 480; // 屏幕高度
D3DXMATRIX matProj;
D3DXMatrixPerspectiveFovH( &matProj,
PI * 0.5f,
(float)nWidth / (float)nHeight,
1.0f,
1000.0f );
pDevice->SetTransform( D3DTS_PROJECTION, &matProj );
4.绘制3D图形
现在我们有了三角形和摄像机了,最后要做的就是把图像绘制渲染出来。但在绘制之前还是要做一些准备工作,如下:
(1)设置资源流。设置资源流就是告诉Direct3D我们要绘制哪些3D图元,也就是把一个个顶点缓冲区挂钩起来。这里我们要绘制的是一个三角形。
(2)设置顶点格式。
整个绘制的过程如下:
a.清屏
b.开始场景
c.设置资源流和设置顶点格式
d.绘制
e.结束场景
f.显示图像
看到这个过程读者或许会觉得奇怪,步骤d进行了绘制,那么步骤f的显示图像又是什么呢?如果你还记得上一期所介绍的双缓冲技术的话,现在也许你已经知道怎么回事了。步骤f进行的绘制只是在后台缓冲进行的,并没有真正显示在屏幕上,到了步骤f才真正把图像显示在屏幕上。绘制过程的代码如下:
// 清屏
pDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
// 开始场景
Device->BeginScene();
//设置资源流和设置顶点格式
Device->SetStreamSource(0, vbTriangle, 0, sizeof(Vertex));
Device->SetFVF(FVF_VERTEX);
// 绘制
Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
// 结束场景
Device->EndScene();
// 显示图像
Device->Present(0, 0, 0, 0);
5.角色的移动——变换
你在玩3D游戏时,你的游戏角色在游戏场景里跑,与敌人打斗,或在空中非常酷的来个360度转身。实现这一系列的动作其实就是在做一系列的变换计算。这里的变换指的是平移变换、旋转变换、缩放变换。所有的变换都是通过一个4×4的矩阵来计算的。有关矩阵知识可以查阅线性代数的书籍。
假设我们在3D场景里有一只茶壶。平移变换是指把茶壶从一个坐标移动到另一个坐标。旋转变换是指把茶壶围绕X轴,Y轴或Z轴旋转一个角度,当然也能围绕一条自定义的轴进行旋转。缩放变换是指把茶壶进行放大缩小。
6.运用灯光
在拍摄电影时,灯光起了很大的作用,也有专门的灯光师去设置灯光。而在3D游戏中,加上灯光效果我们的世界就逼真起来了,加上灯光后3D场景的立体效果便呈现出来,具有层次感,如图3所示。

说到灯光就必须说一说材质。在真实世界中,每种物体都有自己的材质,如塑料、金属、木料等等。材质的属性分别有在漫射光下的颜色、环境光下的颜色、镜面光下的颜色、高光值等等。如果在3D场景中应用了灯光,就需要为3D场景里的每一个物体设置材质。
7.为图形加上纹理
现在大家都应该知道,我们在3D游戏里所看到的游戏角色只不过是一个3D网格,但这还不完全正确,正确的描述应该是披了一层“皮”的3D网格。这里所说的“皮”指的是纹理,纹理其实是一幅图像。为3D网格加上纹理后,能够更细腻更真正地表现出3D场景。在图4所看到的是一个附上木纹纹理的立方体,这让它成为一个木箱子。

三、正确的学习途径
学习Direct3D,国外的一本书籍《Introduction to 3D Game programming with DirectX 9.0》是很不错的教材。在国内只有这本书的电子版,也有人对它进行了中文翻译,大家可以上游戏开发资源网(www.gameres.com)上找找。花两个月时间好好看看这本书和练习里面的实例,再花两个月时间自己开发一些3D游戏图形进行实践。另外,DirectX SDK所提供的Direct3D的实例非常好,花时间去研究必定可以获得提高。
在门外看别人开发3D游戏时,会觉得很有趣。但当试图走进门里面,自己亲自进行开发时,情况就不一样了,你必须忍受一些枯燥乏味的3D知识,如果读者有兴趣深入学习3D知识的话,建议去找有关计算机图形学、线性代数与空间解析几何的书籍看看。