转自:http://blog.163.com/bingcaihuang@126/blog/static/19894212201001824828380/
DXUT是什么?
DXUT即DirectX Utility Library
它是微软为DirectX的Samples写的一个框架,有了这个框架,Sample的构建就方便多了,这个框架实际上抽取了所有构建Sample 都要用到的代码,比如处理窗口消息,处理设备丢失与重设等等。这样一来,我们就可以从繁重的重复代码中解脱出来,专注于编写customer code.
其实题目本来想写如何使用DXUT中的Camera,因为我觉得camera才是本文的精华。
下面我们就看一下如何用DXUT框架造自己的DirectX程序。在这里我使用的DirectX SDK版本是March 2008, 编译器是VSTS2008
想要使用DXUT,必须安装DirectX SDK,这里下载
首先,启动DirectX Sample Browser(开始菜单,DirectX目录下),在里面找到"Empty Project"工程,点击InstallProject按钮来安装它

安装提示后会提示是否打开工程目录,打开工程之后,你会看到这个空的工程已经包含了许多代码,但是这些都是框架代码,没有做任何实际的渲染,你可以编译并运行这个程序,当然你只会得到一个蓝色的窗口,里面空无一物。也很美,不是么?

不要小看这个毫无内容的窗口,实际上DUXT已经为你完成了许多棘手的工作,比如处理窗口大小变化的代码,如果自己写,是要费一番功夫的。好了,下面我们来润色一下,添加点东西进去,就像许多程序设计语言的第一课都以hello world为例一样,大多数图形学教程的第一个模型都是以teapot为例,我们也不例外,就画一个茶壶吧。
在D3D中,绘制一个model大体要这么四步,我们就以茶壶为例,这四步搞懂了,下面就好理解了
1. 定义mesh
2. 创建mesh
3. 绘制mesh
4. 释放mesh
说点题外话,什么是mesh
说了如此多的mesh,那么到底什么是mesh呢?我在刚刚接触这个词的时候,也很茫然,这个词的中文译名基本是网格。在D3D中,最小的几何图元是三角形,说它最小,是因为不可再细分了,也就是说在D3D中,无论多么复杂的模型,最终都可以分解为若干个三角形,这个并不难理解,比如我想画一个正方形,就可以使用两个等腰直角三角形拼接一个,我想画个立方体,则可以用若干个正方形来拼接。即使是一个球体,也可以用若干个三角形组合而成,这么多的三角形放到一起,看起来就行成了很多网格,我想大概这就是mesh的来历吧。

对比下面两张图,你可以更清楚的明白mesh的含义
模型

对应的网格
在OnD3D9ResetDevice函数中设置投影矩阵
1 float fAspectRatio = pBackBufferSurfaceDesc->Width / (FLOAT)pBackBufferSurfaceDesc->Height;
2 modelCamera.SetProjParams( D3DX_PI/4, fAspectRatio, 0.1f, 1000.0f );
3 modelCamera.SetWindow( pBackBufferSurfaceDesc->Width, pBackBufferSurfaceDesc->Height );
4
注意,到这里时,上面那个自定义的SetupMatrix函数就可以不用了,因为我们使用Camera来处理矩阵了
在OnFrameMove函数中调用camera的FrameMove函数
1 modelCamera.FrameMove(fElapsedTime);
在MsgProc函数中调用camera的消息处理函数,这使得model camera可以处理鼠标消息
1 modelCamera.HandleMessages(hWnd, uMsg, wParam, lParam) ;
最后,也是最重要的,是OnD3D9FrameRender函数中的代码
在每一帧开始绘制之前,我们要取得当前的矩阵并应用到当前的Scene中
1 // Set world matrix
2 D3DXMATRIX world = *modelCamera.GetWorldMatrix() ;
3 pd3dDevice->SetTransform(D3DTS_WORLD, &world) ;
4
5 // Set view matrix
6 D3DXMATRIX view = *modelCamera.GetViewMatrix() ;
7 pd3dDevice->SetTransform(D3DTS_VIEW, &view) ;
8
9 // Set projection matrix
10 D3DXMATRIX proj = *modelCamera.GetProjMatrix() ;
11 pd3dDevice->SetTransform(D3DTS_PROJECTION, &proj) ;
12

以下所提到的函数,都是EmptyProject.cpp中的代码
好了,下面开始绘制茶壶的详细步骤。
首先声明一个全局变量,用来保存茶壶对应的mesh
1 ID3DXMesh* mesh = 0; // mesh pointer for teapot
然后,在OnD3D9CreateDevice函数中创建teapot
1 D3DXCreateTeapot(pd3dDevice, &mesh, NULL) ;
在OnD3D9FrameRender函数中绘制teapot,注意,绘制代码一定要放在BeginScene和EndScene之间
1 // Render the scene
2 if( SUCCEEDED( pd3dDevice->BeginScene() ) )
3 {
4 mesh->DrawSubset(0) ; //绘制茶壶
5 V( pd3dDevice->EndScene() );
6 }
7
最后,当绘制结束时要清理mesh,在OnD3D9DestroyDevice函数中释放mesh
1 if(mesh != NULL)
2 mesh->Release() ;//释放mesh
3
好了,编译运行。
如果不出意外的话,你肯定看不到茶壶,而是一条黑线

为什么呢?再漂亮的美女,站在你背后,你也欣赏不到美,计算机就更笨了,你得告诉它看什么,向哪里看,这就需要设置设置view matrix和projection matrix,现在来设置它们,定义一个函数SetupMatrix,如下
1 VOID SetupMatrix(IDirect3DDevice9* pd3dDevice)
2 {
3 // Set view matrix
4 D3DXVECTOR3 vEyePt( 0.0f, 0.0f,-5.0f ); //眼睛的位置
5 D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f ); //视点中心
6 D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f ); //向上向量
7 D3DXMATRIXA16 matView;
8 D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
9 pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
10
11 // Set projection matrix
12 D3DXMATRIXA16 matProj;
13 D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 1000.0f );
14 pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
15
16 }
并在OnD3D9FrameRender函数中调用之
1 SetupMatrix(pd3dDevice) ; // 调用自定义的SetupMatrix函数
2
3 // Render the scene
4 if( SUCCEEDED( pd3dDevice->BeginScene() ) )
5 {
6 mesh->DrawSubset(0) ;
7 V( pd3dDevice->EndScene() );
8 }
9
好了,再次编译并运行,哇,茶壶出现了!

可是仍然是黑色的,黑的不好看,来个彩色的吧,OK,继续!
要想使Model丰富多彩,就要设置材质(material)和光照,这两个属性决定了Model的最终颜色,简单解释一下这两个属性,和现实生活中的情况是一样的。同一束光照在不同的材质上效果是不同的,比如一束光在石头上和照在玻璃上效果肯定不一样。所以材质是决定光照的一个因素,也就是光的反射程度,映射到D3D中就是设置材质的漫射光属性。另一个就是关照本身了,在这里我们使用平行光。于是添加如下函数设置光照效果。diffuse表示漫射光,而ambient表示环境光,一般将他们的值设为相同r,g,b分别表示红,绿,蓝三种颜色的程度,范围是0.0-1.0,在这里r和g设置为1.0 而b设置为0.0,实际上构造了一个反射黄色光的材质,也就是我们最终看见的model颜色将是黄色。
再将入射光定为白色,方向指向x轴正向。
一般来说,D3D程序通常将入射光设置为白色,而通过设置材质来决定最终的反射光颜色,本例即如此。
1 VOID SetupLight(IDirect3DDevice9* pd3dDevice)
2 {
3 // Set material
4 D3DMATERIAL9 mtrl;
5 ZeroMemory( &mtrl, sizeof(mtrl) );
6 mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
7 mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
8 mtrl.Diffuse.b = mtrl.Ambient.b = 0.0f;
9 mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
10 pd3dDevice->SetMaterial( &mtrl );
11
12
13 // Set light
14 D3DXCOLOR color(1.0f, 1.0f, 1.0f, 0.0f) ;
15 D3DXVECTOR3 dir(1.0f, 0.0f, 0.0f ) ;
16
17 D3DLIGHT9 light ;
18 ZeroMemory(&light, sizeof(light)) ;
19 light.Type = D3DLIGHT_DIRECTIONAL ;
20 light.Ambient = color * 0.6f;
21 light.Diffuse = color;
22 light.Specular = color * 0.6f;
23 light.Direction = dir ;
24 pd3dDevice->SetLight(0, &light) ; // Set light ;
25 pd3dDevice->LightEnable(0, true) ; // Enable light
26
27 pd3dDevice->SetRenderState(D3DRS_LIGHTING , TRUE);
28 pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true);
29 }
在OnD3D9FrameRender函数中调用之
1 SetupMatrix(pd3dDevice) ;
2 SetupLight(pd3dDevice) ;
3
再次编译运行,哇,经典的蓝黄配!

但是这个茶壶是固定的,如何让它动起来呢,使之可以相应用户的输入。这就需要用到DXUT的Camera类,这里我们使用 ModelViewCamera来实现,默认情况下,生成的EmptyProject是不包含Camera类的,所以我们要手动添加,来到程序所在目录,将DXUT-Optional文件夹下的DXUTres.h,DXUTres.cpp,DXUTcamera.h和DXUTcamera.cpp四个文件加入到工程中的DXUT文件夹下
在EmptyProject.cpp文件头部加上下面一句,以便可以使用camera类
1 #include "DXUTCamera.h"
定义一个全局变量保存model camera
1 CModelViewerCamera modelCamera ;
在OnD3D9CreateDevice函数中设置view matrix
eyePt 眼睛 位于z轴的负半轴5个单位距离处,DirectX使用左手系,z轴正向指向屏幕内部。
lookAt 视点 位于坐标原点
通过这个设置,我们相当于在z轴负半轴5个单位距离处朝坐标原点看
至于向上向量,camera类会为我们设置,一般是 D3DXVECTOR3 up(0.0f, 1.0f, 0.0f)
1 D3DXVECTOR3 eyePt(0.0f, 0.0f, -5.0f);
2 D3DXVECTOR3 lookAt(0.0f, 0.0f, 0.0f);
3 modelCamera.SetViewParams(&eyePt, &lookAt) ;
4
在OnD3D9ResetDevice函数中设置投影矩阵
1 float fAspectRatio = pBackBufferSurfaceDesc->Width / (FLOAT)pBackBufferSurfaceDesc->Height;
2 modelCamera.SetProjParams( D3DX_PI/4, fAspectRatio, 0.1f, 1000.0f );
3 modelCamera.SetWindow( pBackBufferSurfaceDesc->Width, pBackBufferSurfaceDesc->Height );
4
注意,到这里时,上面那个自定义的SetupMatrix函数就可以不用了,因为我们使用Camera来处理矩阵了
在OnFrameMove函数中调用camera的FrameMove函数
1 modelCamera.FrameMove(fElapsedTime);
在MsgProc函数中调用camera的消息处理函数,这使得model camera可以处理鼠标消息
1 modelCamera.HandleMessages(hWnd, uMsg, wParam, lParam) ;
最后,也是最重要的,是OnD3D9FrameRender函数中的代码
在每一帧开始绘制之前,我们要取得当前的矩阵并应用到当前的Scene中
1 // Set world matrix
2 D3DXMATRIX world = *modelCamera.GetWorldMatrix() ;
3 pd3dDevice->SetTransform(D3DTS_WORLD, &world) ;
4
5 // Set view matrix
6 D3DXMATRIX view = *modelCamera.GetViewMatrix() ;
7 pd3dDevice->SetTransform(D3DTS_VIEW, &view) ;
8
9 // Set projection matrix
10 D3DXMATRIX proj = *modelCamera.GetProjMatrix() ;
11 pd3dDevice->SetTransform(D3DTS_PROJECTION, &proj) ;
12
好了,运行程序看看,貌似和前一次没什么区别啊?区别大了!现在你可以:
拖动鼠标左键旋转model,注意旋转model时光照效果不变
拖动鼠标右键旋转camera,注意旋转camera时光照会发生明暗变化,因为我们的视点变了,这就好比一束光照向茶壶的正面,茶壶不动,光的方向也不改变,而你作为观察者围着茶壶转,当你转到茶壶背面时,你将看到比较暗的一面。
滑动鼠标滚轮,向前滑动时,是zoom in,茶壶会被放大,向后滑动时,是zoom out,茶壶会被缩小。
