本章我们将介绍使用Direct3D 创建三维游戏的基础,四大变换的概念和使用的方方面面。所谓四大变换,其实是作者结合自己的经验,总结出来的对世界变换、取景变换、投影变换和视口变换的总称而已。
就像这个世界上有很多不同的个体,每个个体都有自己独特的局部坐标系, 而每个个体又仅仅是这个世界中的一份子而己。
世界变换包括平移、旋转和缩放变换,我们可以通过D3DX 库中D3DXMatrixTranslation 、D3DXMatrixRotation*和D3DXMatrixScaling 函数来进行变换, 并得到一个世界变换矩阵。
其中D3DXMatrixTranslation 用于矩阵的平移操作, D3DXMatrixRotation* 用于矩阵的旋转操作,D3DXMatrixScaling 用于矩阵的缩放操作(稍后会分别介绍〉。这3 种变换是矩阵的最常用的变换。
调用这些函数将我们矩阵调整好之后, 接着我们就调用IDirect3DDevice9 接口的SetTransform方法来运用世界变换矩阵, 表示认定某某矩阵就是我们的世界变换矩阵了。
我们可以在DirectX SDK 中查到SetTransform 方法的原型:
HRESULT SetTransform(
[in] D3DTRANSFORMSTATETYPE State,
[in] const D3DMATRIX *pMatrix
);
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 ,是一个有实实在在内容的矩阵,显然就是我们想要设置为与第一个参数中指定的类型相挂钩的候选人矩阵了。
接着我们分别来介绍经常会用到的矩阵的一些基本变换方法。
D3DXMatrixTranslation 方法用于矩阵的平移。我们先来看一下这个函数的原型:
D3DXMATRIX * D3DXMatrixTranslation(
__inout D3DXMATRIX *pOut,
__in FLOAT x,
__in FLOAT y,
__in FLOAT z
);
这个函数其实用于创造一个相对于原点(0, 0, 0)有偏移量的矩阵出来。我们先来看一下各个参数的意义。
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
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 mTrans ;
float fAngle = 90 * (2.0f*D3DX_PI) /360.0f;
D3DXMatrixRotationY (&mTrans, fAngle) ;
g_pd3dDevice- >SetTransform (D3DTS_WORLD, &mTrans) ;
D3DXMATRIX * D3DXMatrixScaling(
__inout D3DXMATRIX *pOut,
__in FLOAT sx,
__in FLOAT sy,
__in FLOAT sz
);
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); // 单位化世界矩阵
对于处于不同位置的虚拟摄像机和观察点,其观察物体模型的视角方向也有所差异,因此实际看到的物体模型的实际形状也有所不同。正所谓“横看成岭侧成峰”,就像这幅图所表达的观点一样:
为了确定一个虚拟摄像机的位置和观察方向,需要指定虚拟摄像机在世界坐标系中的位置、观察点位置以及正方向。为了能够进行取景变换,首先需要通过D3DX 库中的D3DXMatrixLookAtLH函数计算并得到一个取景变换矩阵(或观察矩阵〉, 然后同样调用IDirect3DDevice 接口的SetTransform 方法应用取景变换。
其中, D3DXMatrixLookAtLH 函数的声明如下:
D3DXMATRIX * D3DXMatrixLookAtLH(
__inout D3DXMATRIX *pOut,
__in const D3DXVECTOR3 *pEye,
__in const D3DXVECTOR3 *pAt,
__in const D3DXVECTOR3 *pUp
);
然后依然是函数参数的介绍:
//【四大变换之二】:取景变换矩阵的设置
//--------------------------------------------------------------------------------------
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 * D3DXMatrixPerspectiveFovLH(
__inout D3DXMATRIX *pOut,
__in FLOAT fovy,
__in FLOAT Aspect,
__in FLOAT zn,
__in FLOAT zf
);
依旧是一个调用实例:
//【四大变换之三】:投影变换矩阵的设置
//--------------------------------------------------------------------------------------
D3DXMATRIX matProj; //定义一个矩阵
D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f, 1.0f, 1.0f, 1000.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;
每个参数的类型在注释中已经做了解释。
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 的定义也是这样的,可以触类旁通。
//-----------------------------------【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 键在线框填充模式和实体填充模式之间切换。
通过这一章的学习, 我们己经从二维的平面世界转到了三维的立体世界。按这样的进度,离用Direcr3D 绘制出绚丽游戏场景还会远吗?