您好,登錄后才能下訂單哦!
本系列文章由zhmxy555(毛星云)編寫,轉載請注明出處。
作者:毛星云(淺墨) 郵箱: happylifemxy@163.com
本篇文章中,我們以核心思想為突破口,從原理介紹到一個C++類的寫法,一步一步帶領大家實現了一個第一人稱三維攝像機的C++類。然后我們在這個攝像機類的幫助下,放出了一個幾乎貫穿了我們之前學到的所有DirectX相關知識的“三維場景漫游”示例程序,算是對我們之前學的固定功能流水線這套渲染體系的總結。這個“三維場景漫游”示例程序的代碼量有一千行,包括了Direct3D初始化,DirectInput輸入處理,頂點緩存,光照與材質,文字輸出,顏色,紋理貼圖,四大變換,網格模型,X文件載入等等知識(當然還有默認被開啟的深度緩存)。下面我們先放出一張運行截圖:
好吧,正文開始,來卡看如何一步一步實現一個第一人稱三維攝像機。
一、一些概述
回想我們之前文章的配套示例程序中,我們都是通過封裝的DirectInput類來處理鍵盤和鼠標的輸入,對應地改變我們人物模型的世界矩陣來達到移動物體,改變觀察點的效果。其實我們的觀察方向乃至觀察點都是沒有變的,變的只是我們3D人物的位置。舉個例子吧,我們之前的示例程序就像是在小區里面安放著的攝像頭,位置和觀察點都是固定的,變的只是來來往往的人群。貼幾張我們之前文章配套的示例程序截圖來讓大家體會一下:
之前的示例程序,和三維觀察相關的四大矩陣變換我們都封裝在了一個叫Matrix_Set()的函數中,而Matrix_Set()通常只用在初始化資源的時候調用一次而已。其中,四大變換中的取景變換,投影變換和視口變換都是一直老老實實地在Matrix_Set()中盡職盡責地呆著,當然,除了世界矩陣變換這個調皮的野孩子之外。世界矩陣由于實時處理的需要,自從第一次介紹完四大變換的時候讓他在Matrix_Set()中呆了一次,在此之后的示例程序中我們都是把世界矩陣的設置放在Direct3D_Update()以及Direct3D_Render()之中的。因為我們必須實時地處理輸入,讓人物模型的世界矩陣能根據我們的輸入進行相應的調整,這樣才能做出人物模型隨著鍵盤和鼠標的輸入,隨心所欲地受我們控制的效果出來。
但其實我們之前實現的那種所謂的“視角改變”還是來得不徹底不爽快,說白了就是用D3DXMatrixLookAtLH在資源初始化時固定住視角,在程序運行過程中接收到消息并改變三維人物模型的世界矩陣而已。我們這篇文章的主要講解點就是手把手地教大家創建出一個可以在三維空間中自由移動的攝像機類,這樣利用這個攝像機類寫出來的示例程序就像是我們身臨其境地在三維空間中真正地自由翱翔一樣。淺墨怎么在寫上面這段文字的時候,腦海中閃現的竟然是鳳凰傳奇的那句“在你的心上,自由地飛翔”,黑了- -
我們準備給這個攝像機類取名為CameraClass,這個類的功能非常適合用于三維飛行模擬類游戲(比如王牌空戰)以及第一人稱視角游戲(比如反恐精英(CS),穿越火線(CF))中。
二、開始設計攝像機類
首先,給大家介紹一下這個攝像機類的核心思想,那就是用四個分量:右分量(rightvector)、上分量(up vector)、觀察分量(lookvector)和位置分量(position vector),來確定一個攝像機相對于世界坐標系的位置和朝向。并根據這四個分量計算出一個取景變換矩陣,完全取代之前的示例程序用D3DXMatrixLookAtLH創建的取景變換矩陣。
在世界坐標系中,這幾個分量都是通過向量表示的,并且實際上他們為攝像機定義了一個局部坐標系。
其中,攝像機的左分量、上分量和觀察分量定義了攝像機在世界坐標系中的朝向,因此他們也被稱為方向向量。在通常的情況下,方向向量都是單位向量(模為1),并且兩兩之間相互垂直,也就是我們常說的標準正交。
其實,這三個向量我們完全可以理解為三維坐標系中的X,Y,Z軸。
另外,我們需要了解標準正交矩陣的一個重要性質,那就是標準正交矩陣的逆矩陣與其轉置矩陣相等。
用上面提到的右分量(right vector)、上分量(up vector)、觀察分量(look vector)和位置分量(position vector)這四個向量來描述攝像機的話,其中的位置分量其實我們可以把他看做一個描述位置的點,那么有用的就還3個分量,每個分量我們可以進行沿著其平移和繞著其旋轉兩種操作,那么我們可以想到的方式就是2x 3=6種,就是以下這六種運動方式:
● 沿著右分量平移
● 沿著上分量平移
● 沿著觀察分量平移
● 繞著右分量旋轉
● 繞著上分量旋轉
● 繞著觀察分量旋轉
另外,我們新創建的這個攝像機類在我們的示例程序中完全“上位”了,取代了之前我們在Matrix_Set()中實現的取景變換和投影變換,所以在我們新的程序中,Matrix_Set()函數將不復存在,單單用一個攝像機類就行了。
根據上面的講解,我們可以勾勒出這個CameraClass類的輪廓如下:
//============================================================================= // Name: CameraClass.h // Des: 一個封裝了實現虛擬攝像機的類的頭文件 // 2013年 3月10日 Create by 淺墨 //============================================================================= #pragma once #include <d3d9.h> #include <d3dx9.h> class CameraClass { private: //成員變量的聲明 D3DXVECTOR3 m_vRightVector; // 右分量向量 D3DXVECTOR3 m_vUpVector; // 上分量向量 D3DXVECTOR3 m_vLookVector; // 觀察方向向量 D3DXVECTOR3 m_vCameraPosition; // 攝像機位置的向量 D3DXVECTOR3 m_vTargetPosition; //目標觀察位置的向量 D3DXMATRIX m_matView; // 取景變換矩陣 D3DXMATRIX m_matProj; // 投影變換矩陣 LPDIRECT3DDEVICE9 m_pd3dDevice; //Direct3D設備對象 public: //一個計算取景變換的函數 VOID CalculateViewMatrix(D3DXMATRIX *pMatrix); //計算取景變換矩陣 //三個Get系列函數 VOID GetProjMatrix(D3DXMATRIX *pMatrix) { *pMatrix = m_matProj; } //返回當前投影矩陣 VOID GetCameraPosition(D3DXVECTOR3 *pVector) { *pVector = m_vCameraPosition; } //返回當前攝像機位置矩陣 VOID GetLookVector(D3DXVECTOR3 *pVector) { *pVector = m_vLookVector; } //返回當前的觀察矩陣 //四個Set系列函數,注意他們都參數都有默認值NULL的,調用時不寫參數也可以 VOID SetTargetPosition(D3DXVECTOR3 *pLookat = NULL); //設置攝像機的目標觀察位置向量 VOID SetCameraPosition(D3DXVECTOR3 *pVector = NULL); //設置攝像機所在的位置向量 VOID SetViewMatrix(D3DXMATRIX *pMatrix = NULL); //設置取景變換矩陣 VOID SetProjMatrix(D3DXMATRIX *pMatrix = NULL); //設置投影變換矩陣 public: // 沿各分量平移的三個函數 VOID MoveAlongRightVec(FLOAT fUnits); // 沿right向量移動 VOID MoveAlongUpVec(FLOAT fUnits); // 沿up向量移動 VOID MoveAlongLookVec(FLOAT fUnits); // 沿look向量移動 // 繞各分量旋轉的三個函數 VOID RotationRightVec(FLOAT fAngle); // 繞right向量選擇 VOID RotationUpVec(FLOAT fAngle); // 繞up向量旋轉 VOID RotationLookVec(FLOAT fAngle); // 繞look向量旋轉 public: //構造函數和析構函數 CameraClass(IDirect3DDevice9 *pd3dDevice); //構造函數 virtual ~CameraClass(void); //析構函數 };
三、關于向量計算的六個函數講解
下面我們得先介紹一下Direct3D中,與向量計算有關的這六個函數,在寫這個類的過程中有用到。
Ⅰ.D3DXVec3Normalize函數
首先我們來介紹對向量進行規范化的D3DXVec3Normalize函數 ,在MSDN中我們查到它原型如下:
D3DXVECTOR3* D3DXVec3Normalize( _Inout_ D3DXVECTOR3 *pOut, _In_ const D3DXVECTOR3 *pV );這個函數的第一個參數為輸出的結果,在第二個參數中填想要被規范化的向量就行了,一般我們把這兩個參數填一摸一樣的,就表示把填的這個向量規范化后的結果替代原來的向量。
舉個例子就是這樣寫:
//其中的m_vLookVector為向量 D3DXVec3Normalize(&m_vLookVector, &m_vLookVector);//規范化m_vLookVector向量
Ⅱ.D3DXVec3Cross函數
然后我們來介紹用于計算兩個向量叉乘結果的D3DXVec3Cross函數,在MSDN中我們查到它原型如下:
D3DXVECTOR3* D3DXVec3Cross( _Inout_ D3DXVECTOR3 *pOut, _In_ const D3DXVECTOR3 *pV1, _In_ const D3DXVECTOR3 *pV2 );
第一個參數依然是計算的結果。第二和第三兩個參數當然就是填參加叉乘運算的兩個向量了。
另外需要注意,D3DXVec3Cross函數的返回值和第一個參數pOut 參數是一樣的,為指向D3DXVECTOR3 結構的兩個向量叉乘結果。
依然是一個實例:
D3DXVec3Cross(&m_vRightVector, &m_vUpVector,&m_vLookVector); // 右向量與上向量垂直
Ⅲ.D3DXVec3Dot函數
下面我們來講一下用于計算向量點乘的D3DXVec3Dot函數,在MSDN中我查到它的原型如下:
FLOAT D3DXVec3Dot( _In_ const D3DXVECTOR3 *pV1, _In_ const D3DXVECTOR3 *pV2 );
這個函數倒不是和我們上面剛講的那兩個函數一樣有個用于存放結果的pOut,它的結果就存放在返回值中,而兩個參數就填參與運算的兩個向量。
舉一個實例吧:
pMatrix->_42 =-D3DXVec3Dot(&m_vUpVector, &m_vCameraPosition); // -P*U
Ⅳ.D3DXMatrixRotationAxis函數
接下來介紹可以創建一個繞任意軸旋轉一定角度的矩陣的D3DXMatrixRotationAxis函數。其函數原型如下:
D3DXMATRIX* D3DXMatrixRotationAxis( _Inout_ D3DXMATRIX *pOut, _In_ const D3DXVECTOR3 *pV, _In_ FLOAT Angle );
第一個參數顯然就填生成好的矩陣了,第二個參數填要繞著旋轉的那根軸,第三個參數就填上要繞指定的軸旋轉的角度。
依然是一個實例:
D3DXMatrixRotationAxis(&R,&m_vRightVector, fAngle);//創建出繞m_vRightVector旋轉fAngle個角度的R矩陣
Ⅴ.D3DXVec3TransformCoord函數
下面我們看一個D3DXVec3TransformCoord函數,它可以根據給的的矩陣來變換一個向量,并且把變換后的向量規范化后輸出來。這個函數原型如下:
D3DXVECTOR3* D3DXVec3TransformCoord( _Inout_ D3DXVECTOR3 *pOut, _In_ const D3DXVECTOR3 *pV, _In_ const D3DXMATRIX *pM );第一個參數就是得到的結果向量了。第二個參數填要被變換的那個向量,而第三個參數填用于變換的矩陣。
依然是一個實例:
D3DXVec3TransformCoord(&m_vUpVector, &m_vCameraPosition, &R);//讓m_vCameraPosition向量繞m_vRightVector旋轉fAngle個角度
Ⅵ.D3DXVec3Length函數
最后我們介紹計算一個三維向量長度的D3DXVec3Length函數,這個函數原型如下:
FLOAT D3DXVec3Length( _In_ const D3DXVECTOR3 *pV );唯一的一個參數填要計算長度的那個向量,返回值就是計算出的給定向量的三維長度。
依然是一個實例:
float length=D3DXVec3Length(&m_vCameraPosition);
四、計算取景變換矩陣
看完整個CameraClass類的輪廓,下面就準備開始講解其中各個函數的實現代碼應該怎么寫。
首先就是我們最關心的取代了之前用D3DXMatrixLookAtLH無腦計算出取景變換矩陣,而自立門戶的CalculateViewMatrix函數的寫法。
為了講解方便,我們令向量表示位置向量,
向量表示右向量,
向量表示上向量,
向量表示觀察向量。
我們知道,取景變換所解決的其實就是世界坐標系中的物體在以攝像機為中心的坐標系中如何來表示的問題。這就是說,需要將世界坐標系中的物體隨著攝像機一起進行變換,這樣攝像機的坐標系就與世界坐標系完全重合了。
如下圖所示:
上面的(a)圖到(b)圖,是一個平移的過程,而(b)圖到(c)圖則是一個旋轉的過程。另外需要注意的一點是,空間中的物體也應該隨著攝像機一同進行變換,這樣攝像機中看到景物才沒有變化。
我們的目的,就是通過一系列的矩陣變換,得到最終的取景變換矩陣V。
我們要得到取景變換矩陣V,說白了就是能夠滿足如下的條件:
pV=(0,0,0) 矩陣V將攝像機移動到世界坐標系的原點
rV=(1,0,0)矩陣V將攝像機的右向量與世界坐標系的x軸重合
uV=(0,1,0)矩陣V將攝像機的上向量與世界坐標系的y軸重合
lV=(0,0,1)矩陣V將攝像機的觀察向量與世界坐標系的z軸重合
所以,想得到這個取景變換矩陣V,就是進行了先平移,后旋轉,最后綜合的三步曲操作,下面我們來對其各個擊破:
1.平移
將攝像機的位置向量p平移到原點就是實現這個式子:pV=(0,0,0),即攝像機的位置分量乘以V之后等于(0,0,0)。那么很簡單,我們假設取景變換矩陣V實現平移操作的的中間矩陣為T來簡化一下。
而將攝像機的位置向量p平移到原點可以通過把他和它大小相等,方向相反的-p向量做加法輕松實現,即p-p=0,所以描述取景變換中的平移變換部分的T矩陣就是這樣寫:
2.旋轉
想要讓攝像機的各分量與世界坐標系的各軸重合的話,
即滿足之前我們列出的這三個式子:
rV=(1,0,0)
uV=(0,1,0)
lV=(0,0,1)
實現起來,求出如下的一個3x3的矩陣A就可以了:
這里的矩陣A有很多種解法,最科學的解法在這里:
我們一眼就可以看出來A其實就是B矩陣的逆矩陣。而矩陣B剛好是個正交矩陣,我們文章前面就跟大家提到過要用到正交矩陣的這個性質:正交矩陣的逆矩陣等于其轉置矩陣。用到我們這里,就是B矩陣的逆矩陣等于B矩陣的轉置矩陣,而B矩陣的逆矩陣就是A矩陣,那么就是說A矩陣等于B矩陣的轉置矩陣。
那么我們求一下B矩陣的轉置矩陣,就是求出A矩陣了。
也就是:
3.綜合前兩步
我們需要把A矩陣擴展成4X4的,然后計算一下兩矩陣的值TA=V就行了
即:
好了,取景變換矩陣V就被我們求出來了。
上面過程看不太懂不要緊,我們看一下最后V矩陣的結果就行了,下面我們實現計算取景變換矩陣的CalculateViewMatrix函數中,其實也就是用了一下最后我們求出的V矩陣的結果而已:
//----------------------------------------------------------------------------- // Name:CameraClass::CalculateViewMatrix( ) // Desc: 根據給定的矩陣計算出取景變換矩陣 //----------------------------------------------------------------------------- VOID CameraClass::CalculateViewMatrix(D3DXMATRIX *pMatrix) { //1.先把3個向量都規范化并使其相互垂直,成為一組正交矩陣 D3DXVec3Normalize(&m_vLookVector, &m_vLookVector); //規范化觀察分量 D3DXVec3Cross(&m_vUpVector, &m_vLookVector, &m_vRightVector); // 上向量與觀察向量垂直 D3DXVec3Normalize(&m_vUpVector, &m_vUpVector); // 規范化上向量 D3DXVec3Cross(&m_vRightVector, &m_vUpVector, &m_vLookVector); // 右向量與上向量垂直 D3DXVec3Normalize(&m_vRightVector, &m_vRightVector); // 規范化右向量 // 2.創建出取景變換矩陣 //依次寫出取景變換矩陣的第一行 pMatrix->_11 = m_vRightVector.x; // Rx pMatrix->_12 = m_vUpVector.x; // Ux pMatrix->_13 = m_vLookVector.x; // Lx pMatrix->_14 = 0.0f; //依次寫出取景變換矩陣的第二行 pMatrix->_21 = m_vRightVector.y; // Ry pMatrix->_22 = m_vUpVector.y; // Uy pMatrix->_23 = m_vLookVector.y; // Ly pMatrix->_24 = 0.0f; //依次寫出取景變換矩陣的第三行 pMatrix->_31 = m_vRightVector.z; // Rz pMatrix->_32 = m_vUpVector.z; // Uz pMatrix->_33 = m_vLookVector.z; // Lz pMatrix->_34 = 0.0f; //依次寫出取景變換矩陣的第四行 pMatrix->_41 = -D3DXVec3Dot(&m_vRightVector, &m_vCameraPosition); // -P*R pMatrix->_42 = -D3DXVec3Dot(&m_vUpVector, &m_vCameraPosition); // -P*U pMatrix->_43 = -D3DXVec3Dot(&m_vLookVector, &m_vCameraPosition); // -P*L pMatrix->_44 = 1.0f; }代碼中淺墨已經注釋很清楚了,我們先把3個向量都規范化并使其相互垂直,成為一組正交矩陣,然后對著我們計算出的取景變換矩陣V的結果,一行一行賦值就行了。
關于上面的賦值,我們隨便抽一個出來講一下:
比如這句:
pMatrix->_23 = m_vLookVector.y; // Ly
其中的pMatrix->_23表示pMatrix矩陣的第二行,第三行的元素,我們在計算出的取景變換矩陣V的矩陣結果中找到第二行第三列,它的值為ly,也就是上向量m_vLookVector的y坐標值,即m_vLookVector.y,那么第二行第三列就是這樣寫了。
其他行其他列就以此類推了,注意的是一共要寫4x4=16個值。
五、類的其余實現細節
因為這個類淺墨基本上把代碼都逐行注釋了,所以類中其他的函數的實現方法大家直接看代碼就行了。另外在這個類中視口變換并沒有去實現,其實很多時候不用去設置視口的Direct3D就為我們默認好了,不去設置也無傷大雅的。
CameraClass.cpp的所有代碼如下(友情聲明:頭文件CameraClass.h頭文件在上頭我們已經貼出來過了):
//============================================================================= // Name: CameraClass.cpp // Des: 一個封裝了實現虛擬攝像機的類的源文件 // 2013年 3月10日 Create by 淺墨 //============================================================================= #include "CameraClass.h" #ifndef SCREEN_WIDTH #define SCREEN_WIDTH 800 //為窗口寬度定義的宏,以方便在此處修改窗口寬度 #define SCREEN_HEIGHT 600 //為窗口高度定義的宏,以方便在此處修改窗口高度 #endif //----------------------------------------------------------------------------- // Desc: 構造函數 //----------------------------------------------------------------------------- CameraClass::CameraClass(IDirect3DDevice9 *pd3dDevice) { m_pd3dDevice = pd3dDevice; m_vRightVector = D3DXVECTOR3(1.0f, 0.0f, 0.0f); // 默認右向量與X正半軸重合 m_vUpVector = D3DXVECTOR3(0.0f, 1.0f, 0.0f); // 默認上向量與Y正半軸重合 m_vLookVector = D3DXVECTOR3(0.0f, 0.0f, 1.0f); // 默認觀察向量與Z正半軸重合 m_vCameraPosition = D3DXVECTOR3(0.0f, 0.0f, -250.0f); // 默認攝像機坐標為(0.0f, 0.0f, -250.0f) m_vTargetPosition = D3DXVECTOR3(0.0f, 0.0f, 0.0f);//默認觀察目標位置為(0.0f, 0.0f, 0.0f); } //----------------------------------------------------------------------------- // Name:CameraClass::CalculateViewMatrix( ) // Desc: 根據給定的矩陣計算出取景變換矩陣 //----------------------------------------------------------------------------- VOID CameraClass::CalculateViewMatrix(D3DXMATRIX *pMatrix) { //1.先把3個向量都規范化并使其相互垂直,成為一組正交矩陣 D3DXVec3Normalize(&m_vLookVector, &m_vLookVector); //規范化觀察分量 D3DXVec3Cross(&m_vUpVector, &m_vLookVector, &m_vRightVector); // 上向量與觀察向量垂直 D3DXVec3Normalize(&m_vUpVector, &m_vUpVector); // 規范化上向量 D3DXVec3Cross(&m_vRightVector, &m_vUpVector, &m_vLookVector); // 右向量與上向量垂直 D3DXVec3Normalize(&m_vRightVector, &m_vRightVector); // 規范化右向量 // 2.創建出取景變換矩陣 //依次寫出取景變換矩陣的第一行 pMatrix->_11 = m_vRightVector.x; // Rx pMatrix->_12 = m_vUpVector.x; // Ux pMatrix->_13 = m_vLookVector.x; // Lx pMatrix->_14 = 0.0f; //依次寫出取景變換矩陣的第二行 pMatrix->_21 = m_vRightVector.y; // Ry pMatrix->_22 = m_vUpVector.y; // Uy pMatrix->_23 = m_vLookVector.y; // Ly pMatrix->_24 = 0.0f; //依次寫出取景變換矩陣的第三行 pMatrix->_31 = m_vRightVector.z; // Rz pMatrix->_32 = m_vUpVector.z; // Uz pMatrix->_33 = m_vLookVector.z; // Lz pMatrix->_34 = 0.0f; //依次寫出取景變換矩陣的第四行 pMatrix->_41 = -D3DXVec3Dot(&m_vRightVector, &m_vCameraPosition); // -P*R pMatrix->_42 = -D3DXVec3Dot(&m_vUpVector, &m_vCameraPosition); // -P*U pMatrix->_43 = -D3DXVec3Dot(&m_vLookVector, &m_vCameraPosition); // -P*L pMatrix->_44 = 1.0f; } //----------------------------------------------------------------------------- // Name:CameraClass::SetTargetPosition( ) // Desc: 設置攝像機的觀察位置 //----------------------------------------------------------------------------- VOID CameraClass::SetTargetPosition(D3DXVECTOR3 *pLookat) { //先看看pLookat是否為默認值NULL if (pLookat != NULL) m_vTargetPosition = (*pLookat); else m_vTargetPosition = D3DXVECTOR3(0.0f, 0.0f, 1.0f); m_vLookVector = m_vTargetPosition - m_vCameraPosition;//觀察點位置減攝像機位置,得到觀察方向向量 D3DXVec3Normalize(&m_vLookVector, &m_vLookVector);//規范化m_vLookVector向量 //正交并規范化m_vUpVector和m_vRightVector D3DXVec3Cross(&m_vUpVector, &m_vLookVector, &m_vRightVector); D3DXVec3Normalize(&m_vUpVector, &m_vUpVector); D3DXVec3Cross(&m_vRightVector, &m_vUpVector, &m_vLookVector); D3DXVec3Normalize(&m_vRightVector, &m_vRightVector); } //----------------------------------------------------------------------------- // Name:CameraClass::SetCameraPosition( ) // Desc: 設置攝像機所在的位置 //----------------------------------------------------------------------------- VOID CameraClass::SetCameraPosition(D3DXVECTOR3 *pVector) { D3DXVECTOR3 V = D3DXVECTOR3(0.0f, 0.0f, -250.0f); m_vCameraPosition = pVector ? (*pVector) : V;//三目運算符,如果pVector為真的話,返回*pVector的值(即m_vCameraPosition=*pVector),否則返回V的值(即m_vCameraPosition=V) } //----------------------------------------------------------------------------- // Name:CameraClass::SetViewMatrix( ) // Desc: 設置取景變換矩陣 //----------------------------------------------------------------------------- VOID CameraClass::SetViewMatrix(D3DXMATRIX *pMatrix) { //根據pMatrix的值先做一下判斷 if (pMatrix) m_matView = *pMatrix; else CalculateViewMatrix(&m_matView); m_pd3dDevice->SetTransform(D3DTS_VIEW, &m_matView); //把取景變換矩陣的值分下來分別給右分量,上分量,和觀察分量 m_vRightVector = D3DXVECTOR3(m_matView._11, m_matView._12, m_matView._13); m_vUpVector = D3DXVECTOR3(m_matView._21, m_matView._22, m_matView._23); m_vLookVector = D3DXVECTOR3(m_matView._31, m_matView._32, m_matView._33); } //----------------------------------------------------------------------------- // Name:CameraClass::SetProjMatrix( ) // Desc: 設置投影變換矩陣 //----------------------------------------------------------------------------- VOID CameraClass::SetProjMatrix(D3DXMATRIX *pMatrix) { //判斷值有沒有,沒有的話就計算一下 if (pMatrix != NULL) m_matProj = *pMatrix; else D3DXMatrixPerspectiveFovLH(&m_matProj, D3DX_PI / 4.0f, (float)((double)SCREEN_WIDTH/SCREEN_HEIGHT), 1.0f, 30000.0f);//視截體遠景設為30000.0f,這樣就不怕看不到遠處的物體了 m_pd3dDevice->SetTransform(D3DTS_PROJECTION, &m_matProj);//設置投影變換矩陣 } //----------------------------------------------------------------------------- // Name:CameraClass::MoveAlongRightVec( ) // Desc: 沿右向量平移fUnits個單位 //----------------------------------------------------------------------------- VOID CameraClass::MoveAlongRightVec(FLOAT fUnits) { //直接乘以fUnits的量來累加就行了 m_vCameraPosition += m_vRightVector * fUnits; m_vTargetPosition += m_vRightVector * fUnits; } //----------------------------------------------------------------------------- // Name:CameraClass::MoveAlongUpVec( ) // Desc: 沿上向量平移fUnits個單位 //----------------------------------------------------------------------------- VOID CameraClass::MoveAlongUpVec(FLOAT fUnits) { //直接乘以fUnits的量來累加就行了 m_vCameraPosition += m_vUpVector * fUnits; m_vTargetPosition += m_vUpVector * fUnits; } //----------------------------------------------------------------------------- // Name:CameraClass::MoveAlongLookVec( ) // Desc: 沿觀察向量平移fUnits個單位 //----------------------------------------------------------------------------- VOID CameraClass::MoveAlongLookVec(FLOAT fUnits) { //直接乘以fUnits的量來累加就行了 m_vCameraPosition += m_vLookVector * fUnits; m_vTargetPosition += m_vLookVector * fUnits; } //----------------------------------------------------------------------------- // Name:CameraClass::RotationRightVec( ) // Desc: 沿右向量旋轉fAngle個弧度單位的角度 //----------------------------------------------------------------------------- VOID CameraClass::RotationRightVec(FLOAT fAngle) { D3DXMATRIX R; D3DXMatrixRotationAxis(&R, &m_vRightVector, fAngle);//創建出繞m_vRightVector旋轉fAngle個角度的R矩陣 D3DXVec3TransformCoord(&m_vUpVector, &m_vUpVector, &R);//讓m_vUpVector向量繞m_vRightVector旋轉fAngle個角度 D3DXVec3TransformCoord(&m_vLookVector, &m_vLookVector, &R);//讓m_vLookVector向量繞m_vRightVector旋轉fAngle個角度 m_vTargetPosition = m_vLookVector * D3DXVec3Length(&m_vCameraPosition);//更新一下觀察點的新位置(方向乘以模=向量) } //----------------------------------------------------------------------------- // Name:CameraClass::RotationUpVec( ) // Desc: 沿上向量旋轉fAngle個弧度單位的角度 //----------------------------------------------------------------------------- VOID CameraClass::RotationUpVec(FLOAT fAngle) { D3DXMATRIX R; D3DXMatrixRotationAxis(&R, &m_vUpVector, fAngle);//創建出繞m_vUpVector旋轉fAngle個角度的R矩陣 D3DXVec3TransformCoord(&m_vRightVector, &m_vRightVector, &R);//讓m_vRightVector向量繞m_vUpVector旋轉fAngle個角度 D3DXVec3TransformCoord(&m_vLookVector, &m_vLookVector, &R);//讓m_vLookVector向量繞m_vUpVector旋轉fAngle個角度 m_vTargetPosition = m_vLookVector * D3DXVec3Length(&m_vCameraPosition);//更新一下觀察點的新位置(方向乘以模=向量) } //----------------------------------------------------------------------------- // Name:CameraClass::RotationLookVec( ) // Desc: 沿觀察向量旋轉fAngle個弧度單位的角度 //----------------------------------------------------------------------------- VOID CameraClass::RotationLookVec(FLOAT fAngle) { D3DXMATRIX R; D3DXMatrixRotationAxis(&R, &m_vLookVector, fAngle);//創建出繞m_vLookVector旋轉fAngle個角度的R矩陣 D3DXVec3TransformCoord(&m_vRightVector, &m_vRightVector, &R);//讓m_vRightVector向量繞m_vLookVector旋轉fAngle個角度 D3DXVec3TransformCoord(&m_vUpVector, &m_vUpVector, &R);//讓m_vUpVector向量繞m_vLookVector旋轉fAngle個角度 m_vTargetPosition = m_vLookVector * D3DXVec3Length(&m_vCameraPosition);//更新一下觀察點的新位置(方向乘以模=向量) } //----------------------------------------------------------------------------- // Desc: 析構函數 //----------------------------------------------------------------------------- CameraClass::~CameraClass(void) { }
怎么樣,注釋夠詳細吧,類的實現代碼其實就是對著寫好的頭文件挨著挨著把函數擴展實現出來就OK了。
這個類的使用方面的話,我們一般就是在在給繪制做準備的Objects_Init()函數中調用一下,這就是這樣子寫:
// 創建并初始化虛擬攝像機 g_pCamera = new CameraClass(g_pd3dDevice); g_pCamera->SetCameraPosition(&D3DXVECTOR3(0.0f, 200.0f, -300.0f)); //設置攝像機所在的位置 g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 300.0f, 0.0f)); //設置目標觀察點所在的位置 g_pCamera->SetViewMatrix(); //設置取景變換矩陣 g_pCamera->SetProjMatrix(); //設置投影變換矩陣
好了,看完攝像機類的實現,下面我們就放出這個綜合性非常強的“場景漫游”示例程序。
五、詳細注釋的源代碼欣賞
在為大家寫這個示例程序的時候,淺墨感覺自己在創造整個世界,非常地有成就感。首先是選擇天藍色的清屏顏色,這樣我們的“世界”看起來就像是在藍天晴空的背景下了。然后是選擇翠綠色的可以無縫銜接的草坪紋理,用之前學的紋理映射和頂點緩存的知識把紋理重復鋪上個幾百張(具體張數是50x 50=250張),這樣就是一大片綠油油的草坪了。接著就是我們幾節之前剛講的載入X文件模型,淺墨選的人物模型是PS3版《真三國無雙6》中的王元姬,一襲藍色的衣服,非常好看。再接著我們還用D3DXCreateCylinder函數創建了一根柱子(后面用for循環繪制了12根一摸一樣的柱子),最后就是設置光照,設置渲染狀態什么的。
先是用的紋理素材: 尺寸是512x512
然后是PS3版《真三國無雙6》中的王元姬在3DS Max中的渲染效果圖:
也先放一張程序的運行截圖:
本篇文章的配套源代碼包含了6個文件,主要用于公共輔助宏定義的D3DUtil.h,用于封裝了DirectInput輸入控制API的DirectInputClass.h和DirectInputClass.cpp,以及封裝了虛擬攝像機類的CameraClass.h和CameraClass.cpp,當還還有核心代碼main.cpp。
DirectInputClass.h和DirectInputClass.cpp較之前的文章中依然沒有任何修改,依然不再貼出,CameraClass.cpp和CameraClass.h在上面講解的過程中以及貼出來了,這里也不貼出,我們依然只貼核心代碼main.cpp,大家要看得爽的話,源代碼在文章末尾有下載鏈接,下回去用VisualStuido看就行了。下面就是main.cpp的全部代碼:
//***************************************************************************************** // //【Visual C++】游戲開發筆記系列配套源碼四十七 淺墨DirectX教程十五 翱翔于三維空間:第一人稱攝像機的實現 // VS2010版 // 2013年 3月10日 Create by 淺墨 //圖標素材出處: VAMPIRE_SWEETIE //背景音樂素材出處:雅尼-蘭花 //人物模型素材出處:真三國無雙6 王元姬 仿制版 // //***************************************************************************************** //***************************************************************************************** // Desc: 宏定義部分 //***************************************************************************************** #define SCREEN_WIDTH 800 //為窗口寬度定義的宏,以方便在此處修改窗口寬度 #define SCREEN_HEIGHT 600 //為窗口高度定義的宏,以方便在此處修改窗口高度 #define WINDOW_TITLE _T("【Visual C++】游戲開發筆記系列配套示例程序四十七 淺墨DirectX教程十五 翱翔于三維空間:第一人稱攝像機的實現") //為窗口標題定義的宏 //***************************************************************************************** // Desc: 頭文件定義部分 //***************************************************************************************** #include <d3d9.h> #include <d3dx9.h> #include <tchar.h> #include <time.h> #include "DirectInputClass.h" #include "CameraClass.h" //***************************************************************************************** // Desc: 庫文件定義部分 //***************************************************************************************** #pragma comment(lib,"d3d9.lib") #pragma comment(lib,"d3dx9.lib") #pragma comment(lib, "dinput8.lib") // 使用DirectInput必須包含的庫文件,注意這里有8 #pragma comment(lib,"dxguid.lib") #pragma comment(lib, "winmm.lib") //定義頂點結構體 struct CUSTOMVERTEX { FLOAT _x, _y, _z; FLOAT _nx, _ny, _nz; FLOAT _u, _v; CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT nx, FLOAT ny, FLOAT nz, FLOAT u, FLOAT v) { _x = x, _y = y, _z = z; _nx = nx, _ny = ny, _nz = nz; _u = u, _v = v; } }; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1) //***************************************************************************************** // Desc: 全局變量聲明部分 //***************************************************************************************** LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D設備對象 LPD3DXFONT g_pTextFPS =NULL; //字體COM接口 LPD3DXFONT g_pTextAdaperName = NULL; // 顯卡信息的2D文本 LPD3DXFONT g_pTextHelper = NULL; // 幫助信息的2D文本 LPD3DXFONT g_pTextInfor = NULL; // 繪制信息的2D文本 float g_FPS = 0.0f; //一個浮點型的變量,代表幀速率 wchar_t g_strFPS[50]={0}; //包含幀速率的字符數組 wchar_t g_strAdapterName[60]={0}; //包含顯卡名稱的字符數組 D3DXMATRIX g_matWorld; //世界矩陣 DInputClass* g_pDInput = NULL; //一個DInputClass類的指針 CameraClass* g_pCamera = NULL; LPD3DXMESH g_pMesh = NULL; // 網格對象 D3DMATERIAL9* g_pMaterials = NULL; // 網格的材質信息 LPDIRECT3DTEXTURE9* g_pTextures = NULL; // 網格的紋理信息 DWORD g_dwNumMtrls = 0; // 材質的數目 LPD3DXMESH g_cylinder = NULL; //柱子網格對象 D3DMATERIAL9 g_MaterialCylinder; //材質 LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer = NULL; //繪制草地的頂點緩存對象 LPDIRECT3DTEXTURE9 g_pTexture = NULL; //繪制草地的紋理對象 //***************************************************************************************** // Desc: 全局函數聲明部分 //***************************************************************************************** LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance); HRESULT Objects_Init(); void Direct3D_Render( HWND hwnd); void Direct3D_Update( HWND hwnd); void Direct3D_CleanUp( ); float Get_FPS(); void HelpText_Render(HWND hwnd); //***************************************************************************************** // 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,hInstance))) { MessageBox(hwnd, _T("Direct3D初始化失敗~!"), _T("淺墨的消息窗口"), 0); //使用MessageBox函數,創建一個消息窗口 } PlaySound(L"雅尼 - 蘭花.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP); //循環播放背景音樂 MoveWindow(hwnd,200,50,SCREEN_WIDTH,SCREEN_HEIGHT,true); //調整窗口顯示時的位置,窗口左上角位于屏幕坐標(200,50)處 ShowWindow( hwnd, nShowCmd ); //調用Win32函數ShowWindow來顯示窗口 UpdateWindow(hwnd); //對窗口進行更新,就像我們買了新房子要裝修一樣 //進行DirectInput類的初始化 g_pDInput = new DInputClass(); g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); //消息循環過程 MSG msg = { 0 }; //初始化msg while( msg.message != WM_QUIT ) //使用while循環 { if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看應用程序消息隊列,有消息時將隊列中的消息派發出去。 { TranslateMessage( &msg ); //將虛擬鍵消息轉換為字符消息 DispatchMessage( &msg ); //該函數分發一個消息給窗口程序。 } else { Direct3D_Update(hwnd); //調用更新函數,進行畫面的更新 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,HINSTANCE hInstance) { //-------------------------------------------------------------------------------------- // 【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; //獲取顯卡信息到g_strAdapterName中,并在顯卡名稱之前加上“當前顯卡型號:”字符串 wchar_t TempName[60]=L"當前顯卡型號:"; //定義一個臨時字符串,且方便了把"當前顯卡型號:"字符串引入我們的目的字符串中 D3DADAPTER_IDENTIFIER9 Adapter; //定義一個D3DADAPTER_IDENTIFIER9結構體,用于存儲顯卡信息 pD3D->GetAdapterIdentifier(0,0,&Adapter);//調用GetAdapterIdentifier,獲取顯卡信息 int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);//顯卡名稱現在已經在Adapter.Description中了,但是其為char類型,我們要將其轉為wchar_t類型 MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);//這步操作完成后,g_strAdapterName中就為當前我們的顯卡類型名的wchar_t型字符串了 wcscat_s(TempName,g_strAdapterName);//把當前我們的顯卡名加到“當前顯卡型號:”字符串后面,結果存在TempName中 wcscpy_s(g_strAdapterName,TempName);//把TempName中的結果拷貝到全局變量g_strAdapterName中,大功告成~ if(!(S_OK==Objects_Init())) return E_FAIL; SAFE_RELEASE(pD3D) //LPDIRECT3D9接口對象的使命完成,我們將其釋放掉 return S_OK; } HRESULT Objects_Init() { //創建字體 D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("Calibri"), &g_pTextFPS); D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"華文中宋", &g_pTextAdaperName); D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微軟雅黑", &g_pTextHelper); D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑體", &g_pTextInfor); // 從X文件中加載網格數據 LPD3DXBUFFER pAdjBuffer = NULL; LPD3DXBUFFER pMtrlBuffer = NULL; D3DXLoadMeshFromX(L"WYJ.X", D3DXMESH_MANAGED, g_pd3dDevice, &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); // 讀取材質和紋理數據 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //創建一個D3DXMATERIAL結構體用于讀取材質和紋理信息 g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls]; g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; for (DWORD i=0; i<g_dwNumMtrls; i++) { //獲取材質,并設置一下環境光的顏色值 g_pMaterials[i] = pMtrls[i].MatD3D; g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse; //創建一下紋理對象 g_pTextures[i] = NULL; D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]); } SAFE_RELEASE(pAdjBuffer) SAFE_RELEASE(pMtrlBuffer) // 創建一片草坪,50X50=250張紋理 g_pd3dDevice->CreateVertexBuffer(4 * sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &g_pVertexBuffer, 0); CUSTOMVERTEX *pVertices = NULL; g_pVertexBuffer->Lock(0, 0, (void**)&pVertices, 0); pVertices[0] = CUSTOMVERTEX(-500.0f, 0.0f, -500.0f, 0.0f, 1.0f, 0.0f, 0.0f, 50.0f); pVertices[1] = CUSTOMVERTEX(-500.0f, 0.0f, 500.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); pVertices[2] = CUSTOMVERTEX( 500.0f, 0.0f, -500.0f, 0.0f, 1.0f, 0.0f, 50.0f, 50.0f); pVertices[3] = CUSTOMVERTEX( 500.0f, 0.0f, 500.0f, 0.0f, 1.0f, 0.0f, 50.0f, 0.0f); g_pVertexBuffer->Unlock(); // 創建地板紋理 D3DXCreateTextureFromFile(g_pd3dDevice, L"grass.jpg", &g_pTexture); //創建柱子 D3DXCreateCylinder(g_pd3dDevice, 10.0f, 10.0f, 500.0f, 60, 60, &g_cylinder, 0); g_MaterialCylinder.Ambient = D3DXCOLOR(0.9f, 0.0f, 0.8f, 1.0f); g_MaterialCylinder.Diffuse = D3DXCOLOR(0.9f, 0.0f, 0.8f, 1.0f); g_MaterialCylinder.Specular = D3DXCOLOR(0.9f, 0.2f, 0.9f, 0.9f); g_MaterialCylinder.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.9f, 1.0f); // 設置光照 D3DLIGHT9 light; ::ZeroMemory(&light, sizeof(light)); light.Type = D3DLIGHT_DIRECTIONAL; light.Ambient = D3DXCOLOR(0.7f, 0.7f, 0.7f, 1.0f); light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); light.Specular = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f); light.Direction = D3DXVECTOR3(1.0f, 0.0f, 0.0f); g_pd3dDevice->SetLight(0, &light); g_pd3dDevice->LightEnable(0, true); g_pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true); g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, true); // 創建并初始化虛擬攝像機 g_pCamera = new CameraClass(g_pd3dDevice); g_pCamera->SetCameraPosition(&D3DXVECTOR3(0.0f, 200.0f, -300.0f)); //設置攝像機所在的位置 g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 300.0f, 0.0f)); //設置目標觀察點所在的位置 g_pCamera->SetViewMatrix(); //設置取景變換矩陣 g_pCamera->SetProjMatrix(); //設置投影變換矩陣 // 設置紋理過濾和紋理尋址方式 g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); g_pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR); return S_OK; } void Direct3D_Update( HWND hwnd) { //使用DirectInput類讀取數據 g_pDInput->GetInput(); // 沿攝像機各分量移動視角 if (g_pDInput->IsKeyDown(DIK_A)) g_pCamera->MoveAlongRightVec(-0.3f); if (g_pDInput->IsKeyDown(DIK_D)) g_pCamera->MoveAlongRightVec( 0.3f); if (g_pDInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 0.3f); if (g_pDInput->IsKeyDown(DIK_S)) g_pCamera->MoveAlongLookVec(-0.3f); if (g_pDInput->IsKeyDown(DIK_I)) g_pCamera->MoveAlongUpVec( 0.3f); if (g_pDInput->IsKeyDown(DIK_K)) g_pCamera->MoveAlongUpVec(-0.3f); //沿攝像機各分量旋轉視角 if (g_pDInput->IsKeyDown(DIK_LEFT)) g_pCamera->RotationUpVec(-0.003f); if (g_pDInput->IsKeyDown(DIK_RIGHT)) g_pCamera->RotationUpVec( 0.003f); if (g_pDInput->IsKeyDown(DIK_UP)) g_pCamera->RotationRightVec(-0.003f); if (g_pDInput->IsKeyDown(DIK_DOWN)) g_pCamera->RotationRightVec( 0.003f); if (g_pDInput->IsKeyDown(DIK_J)) g_pCamera->RotationLookVec(-0.001f); if (g_pDInput->IsKeyDown(DIK_L)) g_pCamera->RotationLookVec( 0.001f); //鼠標控制右向量和上向量的旋轉 g_pCamera->RotationUpVec(g_pDInput->MouseDX()* 0.001f); g_pCamera->RotationRightVec(g_pDInput->MouseDY() * 0.001f); //鼠標滾輪控制觀察點收縮操作 static FLOAT fPosZ=0.0f; fPosZ += g_pDInput->MouseDZ()*0.03f; //計算并設置取景變換矩陣 D3DXMATRIX matView; g_pCamera->CalculateViewMatrix(&matView); g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //把正確的世界變換矩陣存到g_matWorld中 D3DXMatrixTranslation(&g_matWorld, 0.0f, 0.0f, fPosZ); //以下這段代碼用于限制鼠標光標移動區域 POINT lt,rb; RECT rect; GetClientRect(hwnd,&rect); //取得窗口內部矩形 //將矩形左上點坐標存入lt中 lt.x = rect.left; lt.y = rect.top; //將矩形右下坐標存入rb中 rb.x = rect.right; rb.y = rect.bottom; //將lt和rb的窗口坐標轉換為屏幕坐標 ClientToScreen(hwnd,<); ClientToScreen(hwnd,&rb); //以屏幕坐標重新設定矩形區域 rect.left = lt.x; rect.top = lt.y; rect.right = rb.x; rect.bottom = rb.y; //限制鼠標光標移動區域 ClipCursor(&rect); } //***************************************************************************************** // 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|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(50, 100, 250), 1.0f, 0); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之二】:開始繪制 //-------------------------------------------------------------------------------------- g_pd3dDevice->BeginScene(); // 開始繪制 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之三】:正式繪制 //-------------------------------------------------------------------------------------- //繪制人物 g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_matWorld);//設置模型的世界矩陣,為繪制做準備 // 用一個for循環,進行模型的網格各個部分的繪制 for (DWORD i = 0; i < g_dwNumMtrls; i++) { g_pd3dDevice->SetMaterial(&g_pMaterials[i]); //設置此部分的材質 g_pd3dDevice->SetTexture(0, g_pTextures[i]);//設置此部分的紋理 g_pMesh->DrawSubset(i); //繪制此部分 } // 繪制草坪 D3DXMATRIX matWorld; D3DXMatrixTranslation(&matWorld, 0.0f, 0.0f, 0.0f); g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld); g_pd3dDevice->SetTexture(0, g_pTexture); g_pd3dDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(CUSTOMVERTEX)); g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX); g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); //繪制柱子 D3DXMATRIX TransMatrix, RotMatrix, FinalMatrix; D3DXMatrixRotationX(&RotMatrix, -D3DX_PI * 0.5f); g_pd3dDevice->SetMaterial(&g_MaterialCylinder); for(int i = 0; i < 6; i++) { D3DXMatrixTranslation(&TransMatrix, -100.0f, 0.0f, -150.0f + (i * 75.0f)); FinalMatrix = RotMatrix * TransMatrix ; g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix); g_cylinder->DrawSubset(0); D3DXMatrixTranslation(&TransMatrix, 100.0f, 0.0f, -150.0f + (i * 75.0f)); FinalMatrix = RotMatrix * TransMatrix ; g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix); g_cylinder->DrawSubset(0); } HelpText_Render(hwnd); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之四】:結束繪制 //-------------------------------------------------------------------------------------- g_pd3dDevice->EndScene(); // 結束繪制 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之五】:顯示翻轉 //-------------------------------------------------------------------------------------- g_pd3dDevice->Present(NULL, NULL, NULL, NULL); // 翻轉與顯示 } void HelpText_Render(HWND hwnd) { //定義一個矩形,用于獲取主窗口矩形 RECT formatRect; GetClientRect(hwnd, &formatRect); //在窗口右上角處,顯示每秒幀數 formatRect.top = 5; int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() ); g_pTextFPS->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA(0,239,136,255)); //顯示顯卡類型名 g_pTextAdaperName->DrawText(NULL,g_strAdapterName, -1, &formatRect, DT_TOP | DT_LEFT, D3DXCOLOR(1.0f, 0.5f, 0.0f, 1.0f)); // 輸出幫助信息 formatRect.left = 0,formatRect.top = 380; g_pTextInfor->DrawText(NULL, L"控制說明:", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(235,123,230,255)); formatRect.top += 35; g_pTextHelper->DrawText(NULL, L" W:向前飛翔 S:向后飛翔 ", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" A:向左飛翔 D:向右飛翔", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" I:垂直向上飛翔 K:垂直向下飛翔", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" J:向左傾斜 L:向右傾斜", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" 上、下、左、右方向鍵、鼠標移動:視角變化 ", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" 鼠標滾輪:人物模型Y軸方向移動", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" ESC鍵 : 退出程序", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); } //***************************************************************************************** // 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接口對象 for (DWORD i = 0; i<g_dwNumMtrls; i++) SAFE_RELEASE(g_pTextures[i]); SAFE_DELETE(g_pTextures); SAFE_DELETE(g_pMaterials); SAFE_DELETE(g_pDInput); SAFE_RELEASE(g_cylinder); SAFE_RELEASE(g_pMesh); SAFE_RELEASE(g_pd3dDevice); SAFE_RELEASE(g_pTextAdaperName) SAFE_RELEASE(g_pTextHelper) SAFE_RELEASE(g_pTextInfor) SAFE_RELEASE(g_pTextFPS) SAFE_RELEASE(g_pd3dDevice) }
本篇文章的示例程序相對來說比較綜合,里面融合了Direct3D的初始化,DirectInput輸入處理,頂點緩存,文字輸出,顏色,光照與材質,紋理貼圖,四大變換,網格模型,X文件載入等等知識(當然還有默認被開啟的深度緩存)。
雖然這聽起來怎么這么地,但是卻是很好理解的,就是在HRESULT Objects_Init()函數中把要繪制的物體都準備好,也把光照、材質、攝像機都初始化好,然后在Direct3D_Render( HWND hwnd);函數中繪制出來就可以了。說白了也就是相對于之前的demo中多繪制了一點東西而已,沒什么特別的。
攝像機類的使用的話,就是在Objects_Init()順帶著中初始化一下,然后在Direct3D_Update( HWND hwnd)函數中配合著DirectInput類對輸出進行處理就好了。
另外需要注意的是,我們把文字幫助信息和FPS的輸出都封裝在了一個叫HelpText_Render的函數中了。因為我們繪制的東西越來越多,為了Direct3D_Render看起來整潔一點,而不是里面大一串內容,是時候把文字幫助信息封裝起來了。
下面來看一下運行截圖,這次的運行效果就可以天馬行空了,可以在無限的空間中任意地遨游,想去哪兒就飛去哪兒。
程序運行后就是這樣,同時一曲唯美的出自輕音樂大師雅尼的《蘭花》映入耳簾:
我們開始在鍵盤上W,A,S,D,I,J,K,L,↑,↓,←,→12個鍵亂按,加上鼠標亂晃:
越飛越遠:
攝像機在藍天之下,草原之上盤旋:
來張正面照:
暮然回首,那人卻在燈火闌珊處:
隔近了看草地就是這樣的:
這一襲藍衣真美:
更多圖片就不貼了,大家在文章末尾下源代碼自己拿回去玩去吧~
另外我們在Direct3D_Update函數中涉及到了將鼠標限制在某一區域之內的寫法,這個其實很好實現的,我們在講解Windows鼠標消息處理的那篇文章中用到過了。
細心的朋友們應該可以發現,這個程序運行起來還是有一點小問題的,當我們對攝像機進行旋轉操作時,視圖有時候會發生傾斜,如果對于飛行射擊游戲,這樣的傾斜是完全沒問題的,但是如果是做CF,CS,鬼泣這類的第一人稱的游戲的話,那就不應該發生這樣的視覺傾斜了。
攝像機之所以發生傾斜是因為我們構造的攝像機的幾個分量是兩兩之間互相垂直的。要想解決這樣的問題,就需要保證每次進行攝像機的左右旋轉時,是繞著垂直于世界坐標系的軸的向量,而這根軸的向量就是世界坐標系的Y軸。這是我們以后待完成的工作了。
文章最后,依舊是放出本篇文章配套源代碼的下載:
本節筆記配套源代碼請點擊這里下載:
【淺墨DirectX提高班】配套源代碼之十五下載
以上就是本節筆記的全部內容,更多精彩內容,且聽下回分解。
淺墨在這里,希望喜歡游戲開發系列文章的朋友們能留下你們的評論,每次淺墨登陸博客看到大家的留言的時候都會非常開心,感覺自己正在傳遞一種信仰,一種精神。
文章最后,依然是【每文一語】欄目,今天的句子是:
我們所有的夢想都可以成真,只要我們有勇氣去追求它們。
All our dreams can come true, if we have the courageto pursue them.
————沃爾特·迪斯尼
下周一,讓我們離游戲開發的夢想更近一步。
下周一,游戲開發筆記,我們,不見不散。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。