您好,登錄后才能下訂單哦!
本系列文章由zhmxy555(毛星云)編寫,轉載請注明出處。
作者:毛星云(淺墨) 郵箱: happylifemxy@163.com
最近幾個星期,不停地收到大家的評論和郵件,大家都說希望淺墨早點講骨骼動畫。本來按淺墨擬定的寫作計劃是把骨骼動畫放到很后面,因為骨骼動畫知識需要前面的網格模型知識為基礎,知識量本身有些大,很可能要占很多次更新的篇幅。
但是看到大家一致的評論,都說非常期待骨骼動畫。那就好吧,我們就來開始慢慢講。一路披荊斬棘,把網格模型相關知識講完,然后消滅掉骨骼動畫這個看似很難的大塊頭。
其實骨骼動畫學起來并不難,而且掌握了非常好玩。一般的三維游戲中都要用到骨骼動畫,沒用到骨骼動畫的三維游戲基本上都是那種什么小飛機啊,空戰之類的小兒科游戲,弱爆了。
骨骼動畫的前置知識是網格模型(把“前置”這兩個字在word中打出來的時候怎么讓我想起了DNF(毒奶粉)里面學技能時的“前置技能”- -)。而網格模型其實是Direct3D中非常重頭的一部分內容,但是縱觀已經與大家見面的十八篇Direct3D教程以來,只有《
【Visual C++】游戲開發筆記四十四 淺墨DirectX教程十二 網格模型和X文件使用面面觀
》是在純粹講解網格模型,所以想要掌握到骨骼動畫,還得先把網格模型這部分再往后學一學,把基礎部分補一補,這得花一些更新的次數。
這篇文章中我們來補的知識就是網格模型的優化以及克隆,然后把之前那篇網格模型文章中講到的X文件的載入方法封裝到了一個類中,以后就可以用幾行代碼,來載入多個3D模型到我們的游戲場景中了。首先放一張配套程序截圖,這次我們載入了三個模型,分別是來自地獄代表隊的地獄惡魔一頭,來自人類代表隊的人族騎士一個,來自天堂代表隊的天堂雷龍一只。
一、網格模型的優化
文章開頭就提到過了,我們之前講過一次網格模型,大家如果不熟練,請移步到《
【Visual C++】游戲開發筆記四十四 淺墨DirectX教程十二 網格模型和X文件使用面面觀
》復習下吧。
無論是用手編(如果你真有這個毅力這么無聊的話)或者使用三維建模軟件(3DS Max,Maya)所創建出的網格模型,都可能包含一些尸位素餐吃白飯的無用頂點和索引,況且網格中屬性緩存中子集的順序也不一定是有序的。所以,如果想要提高游戲程序的運行效率的話,網格模型的優化是可以做出卓越貢獻的一塊大頭。
Direct3D的ID3DXMes接口為我們提供了ID3DXMesh::OptimizeInplace和ID3DXMesh::Optimize這兩種方法來對網格進行優化。首先Optimize大家都知道,是優化的意思。兩者的區別就在這個多出來的Inplace之上,in place,在原位的意思,那么OptimizeInplace函數就是在原來的位置上進行優化,直接對原網格進行優化,而不帶Inplace的Optimize方法就生成一個優化后的新的網格對象。即調用OptimizeInplace函數進行優化,優化的是原網格,而調用Optimize函數進行優化,原網格不變,會返回一個優化后的新網格。
我們先來對OptimizeInplace方法進行剖析,MSDN中我們可以查到它的原型如下:
HRESULT OptimizeInplace( [in] DWORD Flags, [in] const DWORD *pAdjacencyIn, [out] DWORD *pAdjacencyOut, [out] DWORD *pFaceRemap, [out] LPD3DXBUFFER *ppVertexRemap );
■第一個參數,DWORD類型的Flags,表示執行什么類型的優化方法。它在D3DXMESHOPT枚舉中取一個或者多個的值,常用的一些值如下:
網格優化標識
精析
D3DXMESHOPT_COMPACT
從網格中移除沒用的頂點和索引項。
D3DXMESHOPT_ATTRSORT
根據屬性給三角形排序并調整屬性表,這將使DrawSubset方法的繪制執行更有效率。
D3DXMESHOPT_VERTEXCACHE
增加頂點緩存的命中率
D3DXMESHOPT_STRIPREORDER
重組頂點索引,讓三角帶盡可能的長。
D3DXMESHOPT_IGNOREVERTS
只優化索引信息,忽略掉頂點信息。
另外需要注意的是:D3DXMESHOPT_VERTEXCACHE和D3DXMESHOPT_STRIPREORDER這兩者的領域重合了,不能同時使用。
■第二個參數,const DWORD類型的*pAdjacencyIn ,指向優化前的鄰接數組的指針,一般我們這樣填(DWORD*)pAdjacencyBuffer->GetBufferPointer(),其中的pAdjacencyBuffer我們在D3DXLoadMeshFromX中給它附上了被載入網格的鄰接信息,然后GetBufferPointer一下就是指向待優化的鄰接數組的指針了。
■第三個參數,DWORD類型的*pAdjacencyOut,指向優化后的鄰接數組的指針。如果不需要優化后的鄰接信息,設為NULL就好了。
■第四個參數,DWORD類型的*pFaceRemap,用來填充面重影射信息。該數組必須不小于ID3DXMesh::GetNumFaces()。當一個mesh被優化時,由索引緩存定義的面可能被移動;也就是說,在pFaceRemap中的第i項表示第i個原始面被移動后的新索引值。我們一般很少用,取0或者NULL就行了。
■第五個參數,LPD3DXBUFFER類型的*ppVertexRemap,用于保存網格頂點的重映射信息,一般設為0或者NULL就可以了。
講起來好像每個參數都很復雜,其實用起來蠻簡單的,因為不少參數設為0或者NULL就行了,就像這樣:
//其中pAdjacencyBuffer中存放了要被優化的網格的鄰接信息 //優化網格模型 m_pMesh->OptimizeInplace(D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_STRIPREORDER, (DWORD*)pAdjacencyBuffer->GetBufferPointer(),NULL, NULL, NULL );
然后再講一下Optimize方法,在MSDN中查到它的原型如下:
HRESULT Optimize( [in] DWORD Flags, [in] const DWORD*pAdjacencyIn, [in, out] DWORD *pAdjacencyOut, [in, out] DWORD *pFaceRemap, [out] LPD3DXBUFFER*ppVertexRemap, [out] LPD3DXMESH *ppOptMesh );
這個方法的前五個參數和OptimizeInplace方法的的完全一模一樣,大家照著OptimizeInplace看就得了。因為Optimize方法不修改原網格,所以需要額外準備一個參數來存放指向新網格的指針,這就有了它的第六個參數,LPD3DXMESH類型的*ppOptMesh。
另外,關于模型的優化問題。有些模型在制作過程中已經做了優化,所以有可能再優化也優化不出什么內容了。如果是載入一些沒有經過優化或者是粗制濫造的3D模型,優化的效果就體現出來了,可以大大增加每秒渲染的幀數。
二、網格模型的克隆
另外,我們也可能要用到網格的克隆技術。網格的克隆是重新生成網格數據的一種方式。通過這種方式,我們可以創建出一個與原網格相同的網格模型,而且還能通過克隆選項重新指定網格數據的某些特殊的特性。網格的克隆是三維模型常用的一個概念,3DS Max與Maya中就有專門的功能,而在我們Direct3D中,也有專門的函數來應對,他就是ID3DXBaseMesh::CloneMeshFVF方法,我們來講一下:
HRESULT CloneMeshFVF( [in] DWORD Options, [in] DWORD FVF, [in] LPDIRECT3DDEVICE9pDevice, [out, retval] LPD3DXMESH*ppCloneMesh );
■第一個參數,DWORD類型的Options,表示創建克隆網格時的一些選項,在一個龐大的D3DXMESH枚舉體中取值,完整的介紹在這里就免了,沒意義。我們介紹幾個常用的就行了,其他的很少用到,就算用到了,我們可以查閱MSDN:
克隆網格標識
精析
D3DXMESH_32BIT
克隆的網格使用32位索引
D3DXMESH_MANAGED
克隆出的網格的數據的索引緩存用D3DPOOL_MANAGED標識
D3DXMESH_WRITEONLY
克隆出的網格數據只能執行寫操作,不能執行讀操作
D3DXMESH_DYNAMIC
克隆出的網格緩存為動態的
■第二個參數,DWORD類型的FVF,很好理解,就是為克隆出的新網格指定FVF靈活頂點格式,這就說明了克隆出的新網格是完全可以和原網格有不同的FVF靈活頂點格式的。
■第三個參數,LPDIRECT3DDEVICE9類型的pDevice,老朋友了,我們的金鑰匙Direct3D設備指針。
■第四個參數,LPD3DXMESH類型的*ppCloneMesh,指向克隆出的網格模型的指針。我們事先新建一個網格模型指針,然后調用CloneMeshFVF函數的時候填在這里,調用完之后,這個指針就指向新克隆好的網格模型了。
如果大家看起來不太懂,沒關系,我們來看一個調用實例:
//其中的m_pMesh為需要被克隆的原模型指針,為LPD3DXMESH類型 LPD3DXMESH pNewMesh; m_pMesh->CloneMeshFVF(D3DXMESH_MANAGED,m_pMesh->GetFVF() | D3DFVF_NORMAL, g_pd3dDevice, &pNewMesh);
三、X文件模型載入類的設計
上面的這些知識學起來有點悶有木有,下面我們來動手寫點東西吧,然后給我們經過幾次文章的更新構建起來的初具規模的游戲場景demo進一步完善,加入更多功能。這周的任務是把之前的X文件的載入與繪制功能封裝在一個類中,這樣我們就可以在游戲程序中使用這個類,隨便幾行代碼就可以載入并繪制多個好看的3D模型來。
從某種意義上來說,類的出現就是為了避免重復的勞動。在游戲程序的編寫過程中,多個3D模型的載入必然是需要一個或者多個專門來類的管理的,如果像我們之前的那樣,每載入一個3D模型就要定義一堆全局變量,然后重復寫一堆代碼,完全就是亂來了。等到后面需要繪制的東西越來越多。到最后也許我們自己寫出來的程序源碼,都不忍直視了。。。
我們在上一節中的工程中新建一個C++類,命名為XFileModelClass。依然是老規矩,看看有哪些成員變量。
首先呢,理一理思路,我們注意到之前的示例程序中與X模型載入有關的全局變量有這些:
LPD3DXMESH g_pMesh = NULL; // 網格對象 D3DMATERIAL9* g_pMaterials= NULL; //網格的材質信息 LPDIRECT3DTEXTURE9* g_pTextures = NULL; //網格的紋理信息 DWORD g_dwNumMtrls= 0; // 材質的數目
那么我們類成員變量的書寫思路就出來了。
首當其沖的是D3D設備對象m_pd3dDevice,然后網格模型的指針LPD3DXMESH類型的m_pMesh自然要有,接著表示材質數量的m_dwNumMaterials要有,模型材質的結構體實例m_pMaterials要有,最后,自然就是模型紋理的結構體的實例m_pTextures了。
然后成員函數方面,構造函數析構函數依舊顯式地寫出來,然后就是接下來比較關鍵的成員函數了。不過這次的成員函數就兩個就可以了,一個用于載入,一個用于繪制。模型的載入函數我們取名為LoadModelFromXFile,模型的繪制函數我們取名為RenderModel,好了,類輪廓就被我們勾勒出來了。就是如下,即我們貼出XFileModelClass.h的全部代碼:
//============================================================================= // Name: XFileModelClass.h // Des: 一個封裝了X文件載入與渲染功能的類頭文件 // 2013年 4月7日 Create by 淺墨 //============================================================================= #pragma once #include "D3DUtil.h" class XFileModelClass { private: LPDIRECT3DDEVICE9 m_pd3dDevice; //D3D設備對象 LPD3DXMESH m_pMesh; //網格模型對象 DWORD m_dwNumMaterials; //材質的數量 D3DMATERIAL9* m_pMaterials; //模型材質結構體的實例 LPDIRECT3DTEXTURE9 * m_pTextures; //模型紋理結構體的實例 public: XFileModelClass(IDirect3DDevice9 *pd3dDevice); //構造函數 ~XFileModelClass(void); //析構函數 public: HRESULT LoadModelFromXFile(WCHAR* strFilename ); //從.X文件讀取三維模型到內存中 HRESULT RenderModel( ); //渲染三維網格模型 };
四、X文件模型載入類的實現
類輪廓的框架打好了,實現這個類還不是手到擒來的事。X文件模型的載入核心方法當然是已經被我們用了無數次的D3DXLoadMeshFromX方法。依舊是先復習一下我們在之前的示例程序中是如何載入X文件的,那個時候,我們總結了一個模型載入三步曲:
X文件模型載入三步曲之一,加載網格
X文件模型載入三步曲之二,加載材質紋理,
X文件模型載入三步曲之三,繪制
X文件模型載入三步曲的核心代碼如下:
// 三步曲之一,從X文件中加載網格數據 LPD3DXBUFFERpAdjBuffer = NULL; //網格模型鄰接信息 LPD3DXBUFFERpMtrlBuffer = NULL; //存儲網格模型材質的緩存對象 D3DXLoadMeshFromX(L"miki.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 = newLPDIRECT3DTEXTURE9[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) void Direct3D_Render(HWND hwnd) { g_pd3dDevice->Clear(0,NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(100, 100, 100), 1.0f, 0); //三步曲之三,繪制 g_pd3dDevice->BeginScene(); // 開始繪制 //用一個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); } g_pd3dDevice->EndScene(); // 結束繪制 g_pd3dDevice->Present(NULL, NULL, NULL,NULL); // 翻轉與顯示 }
既然核心代碼就是如上的這些,之前的四個全局變量我們已經定義為了類成員變量,那么剩下的封裝起來還不簡單。說白了就是把X文件模型載入三步曲的前兩步的代碼copy到我們自定義的X文件模型載入函數LoadModelFromXFile的函數體中,把文件路徑,即D3DXLoadMeshFromX函數的第一個參數用一個參數來表示,并且把全局的變量名改成類中定義的對應的成員變量名就好了。最后再用一下我們今天所學的網格優化知識,調用一下OptimizeInplace函數,于是LoadModelFromXFile函數的實現就是這樣:
//-------------------------------------------------------------------------------------- // Name:XFileModelClass::LoadModelFromXFile() // Desc: 從.X文件讀取三維模型到內存中 //-------------------------------------------------------------------------------------- HRESULTXFileModelClass::LoadModelFromXFile( WCHAR* strFilename ) { LPD3DXBUFFERpAdjacencyBuffer = NULL; //網格模型鄰接信息 LPD3DXBUFFERpD3DXMtrlBuffer = NULL; //存儲網格模型材質的緩存對象 //從磁盤文件加載網格模型 D3DXLoadMeshFromX(strFilename, D3DXMESH_MANAGED, m_pd3dDevice, &pAdjacencyBuffer, &pD3DXMtrlBuffer,NULL, &m_dwNumMaterials, &m_pMesh ); //讀取材質和紋理數據 D3DXMATERIAL*d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer(); m_pMaterials= new D3DMATERIAL9[m_dwNumMaterials]; m_pTextures = new LPDIRECT3DTEXTURE9[m_dwNumMaterials]; //逐子集提取材質屬性和紋理文件名 for(DWORD i=0; i<m_dwNumMaterials; i++ ) { //獲取材質,并設置一下環境光的顏色值 m_pMaterials[i]= d3dxMaterials[i].MatD3D; m_pMaterials[i].Ambient= m_pMaterials[i].Diffuse; //創建一下紋理對象 m_pTextures[i]= NULL; if(d3dxMaterials[i].pTextureFilename != NULL && strlen(d3dxMaterials[i].pTextureFilename)> 0 ) { //創建紋理 if(FAILED( D3DXCreateTextureFromFileA(m_pd3dDevice,d3dxMaterials[i].pTextureFilename, &m_pTextures[i] ) ) ) { MessageBox(NULL,L"SORRY~!沒有找到紋理文件!", L"XFileModelClass類讀取文件錯誤", MB_OK); } } } //優化網格模型 m_pMesh->OptimizeInplace(D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_STRIPREORDER, (DWORD*)pAdjacencyBuffer->GetBufferPointer(),NULL, NULL, NULL ); returnS_OK; }
接著,網格模型的繪制函數RenderModel的書寫,參照著“X文件模型載入三步曲之三,繪制”,也就是一個for循環,無腦把代碼拷過來,改一下變量名,于是,RenderModel函數就這樣被我們瞬間無恥地寫完了:
//-------------------------------------------------------------------------------------- // Name: XFileModelClass::RenderModel() // Desc: 渲染三維網格模型 //-------------------------------------------------------------------------------------- HRESULT XFileModelClass::RenderModel( ) { for(DWORD i=0; i<m_dwNumMaterials; i++ ) { m_pd3dDevice->SetMaterial(&m_pMaterials[i] ); m_pd3dDevice->SetTexture(0, m_pTextures[i] ); m_pMesh->DrawSubset(i ); } returnS_OK; }
構造函數和析構函數也沒什么技術含量,構造函數就把D3D設備賦值了進來,析構函數就是在釋放一些對象,實現代碼如下:
//----------------------------------------------------------------------------- // Desc: 構造函數 //----------------------------------------------------------------------------- XFileModelClass::XFileModelClass(IDirect3DDevice9*pd3dDevice) { //給各個成員變量賦初值 m_pd3dDevice= pd3dDevice; } //----------------------------------------------------------------------------- // Desc: 析構函數 //----------------------------------------------------------------------------- XFileModelClass::~XFileModelClass(void) { //釋放網格模型材質 SAFE_DELETE_ARRAY(m_pMaterials); //釋放網格模型紋理 if(m_pTextures ) { for(DWORD i = 0; i < m_dwNumMaterials; i++ ) { SAFE_RELEASE(m_pTextures[i]); } SAFE_DELETE_ARRAY(m_pTextures); } //釋放網格模型對象 SAFE_RELEASE(m_pMesh); }
淺墨覺得我們這次的封裝有夠無恥的,就是在“炒現飯”,把之前用過的東西稍微改一下,用一個類把它們“盛”起來,然后就可以利用這個類,無限度地用相同的伎倆,淡淡地寫幾句代碼,來載入無數的X文件模型了。這有點工廠中的加工車間的意思,把經得起推敲的技術弄成一套生產流水線,來批量生產。
五、X文件模型載入類的使用
這個類中,我們簡化到只用四句代碼就能載入并繪制出一個模型來,不過我們演示的是多個X文件模型的載入,所以就不止4行代碼了,而是有幾個模型就乘以4的代碼量,另外我們還要給每個模型設置不同的世界矩陣,不然一會兒我們繪制的模型就擠到一塊兒去了,所以代碼量又稍微加了一點。
Ⅰ.首先,定義一個XFileModelClass類的全局指針實例:
XFileModelClass* g_pXFileModel1 = NULL; //模型類的第一個對象 XFileModelClass* g_pXFileModel2 = NULL; //模型類的第二個對象 XFileModelClass* g_pXFileModel3 = NULL; //模型類的第三個對象
Ⅱ.然后,在初始化階段拿著類指針對象pXFileModel到處“指”,從X文件載入模型:
HRESULT hr; //載入第一個模型 g_pXFileModel1 = new XFileModelClass(g_pd3dDevice); HR( g_pXFileModel1->LoadModelFromXFile(L"knight.X" )); //載入第二個模型 g_pXFileModel2 = new XFileModelClass(g_pd3dDevice); HR( g_pXFileModel2->LoadModelFromXFile(L"dragon.X" )); //載入第三個模型 g_pXFileModel3 = new XFileModelClass(g_pd3dDevice); HR( g_pXFileModel3->LoadModelFromXFile(L"Demon.X" ));
Ⅲ.最后,就是在Render函數中給他們構建不同的世界矩陣,設置一個世界矩陣繪制一次,這樣有順序地做三次,分別繪制出三個模型來:
//以下這段代碼用于繪制游戲角色 D3DXMATRIXmScal,mTrans1,mTrans2,mTrans3,mFinal1,mFinal2,mFinal3; //定義一些矩陣,準備對模型進行矩陣變換 //第一個角色的繪制,首先是“調合”出這個角色合適的世界矩陣,然后再繪制 D3DXMatrixTranslation(&mTrans1,50.0f,1200.0f,0.0f); D3DXMatrixScaling(&mScal,3.0f,3.0f,3.0f); mFinal1=mScal*mTrans1*g_matWorld; g_pd3dDevice->SetTransform(D3DTS_WORLD,&mFinal1);//設置模型的世界矩陣,為繪制做準備 g_pXFileModel1->RenderModel(); //第二個角色的繪制,首先是“調合”出這個角色合適的世界矩陣,然后再繪制 D3DXMatrixTranslation(&mTrans2,200.0f, 0.0f, 0.0f); mFinal2=mTrans2*mFinal1; g_pd3dDevice->SetTransform(D3DTS_WORLD,&mFinal2); g_pXFileModel2->RenderModel(); //第三個角色的繪制,首先是“調合”出這個角色合適的世界矩陣,然后再繪制 D3DXMatrixTranslation(&mTrans3,-200.0f, 0.0f, 0.0f); mFinal3=mTrans3*mFinal1; g_pd3dDevice->SetTransform(D3DTS_WORLD,&mFinal3); g_pXFileModel3->RenderModel();
通過類的封裝,其實一個模型的載入與繪制最簡化可以到兩句代碼,把LoadModelFromXFile函數中的內容直接放在構造函數中,然后在定義類對象的同時用構造函數初始化,然后世界矩陣也不設置直接裸奔繪制,不過這樣的話,這個類就太怪異了- -。
本篇文章配套的源代碼在之前的基礎上又增加了兩個文件,也就是封裝了X文件模型載入類的源文件和頭文件。全部文件數量增加到了14個,它們的列表如下:
我們依舊只貼出核心代碼main.cpp,其他的眾多文件大家下源代碼回去看就好了。
//***************************************************************************************** // //【Visual C++】游戲開發筆記系列配套源碼五十一 淺墨DirectX教程十九 網格模型進階之路 // VS2010版 // 2013年 4月6日 Create by 淺墨 //圖標素材出處: VAMPIRE_SWEETIE //背景音樂素材出處:魔獸爭霸3 暗夜精靈戰斗曲 //人物模型素材出處:英雄無敵6 // //***************************************************************************************** //***************************************************************************************** // Desc: 宏定義部分 //***************************************************************************************** #define SCREEN_WIDTH 932 //為窗口寬度定義的宏,以方便在此處修改窗口寬度 #define SCREEN_HEIGHT 700 //為窗口高度定義的宏,以方便在此處修改窗口高度 #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" #include "TerrainClass.h" #include "SkyBoxClass.h" #include "SnowParticleClass.h" #include "XFileModelClass.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") //***************************************************************************************** // 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; //世界矩陣 D3DLIGHT9 g_Light; //全局光照 DInputClass* g_pDInput = NULL; //DInputClass類的指針實例 CameraClass* g_pCamera = NULL; //攝像機類的指針實例 TerrainClass* g_pTerrain = NULL; //地形類的指針實例 SkyBoxClass* g_pSkyBox=NULL; //天空盒類的指針實例 SnowParticleClass* g_pSnowParticles = NULL; //雪花粒子系統的指針實例 XFileModelClass* g_pXFileModel1 = NULL; //模型類的第一個對象 XFileModelClass* g_pXFileModel2 = NULL; //模型類的第二個對象 XFileModelClass* g_pXFileModel3 = 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,FLOAT fTimeDelta); void Direct3D_Update( HWND hwnd,FLOAT fTimeDelta); 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("GameMedia\\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"GameMedia\\NightElf1.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP); //循環播放背景音樂 MoveWindow(hwnd,200,10,SCREEN_WIDTH,SCREEN_HEIGHT,true); //調整窗口顯示時的位置,窗口左上角位于屏幕坐標(200,0)處 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循環 { static FLOAT fLastTime = (float)::timeGetTime(); static FLOAT fCurrTime = (float)::timeGetTime(); static FLOAT fTimeDelta = 0.0f; fCurrTime = (float)::timeGetTime(); fTimeDelta = (fCurrTime - fLastTime) / 1000.0f; fLastTime = fCurrTime; if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看應用程序消息隊列,有消息時將隊列中的消息派發出去。 { TranslateMessage( &msg ); //將虛擬鍵消息轉換為字符消息 DispatchMessage( &msg ); //該函數分發一個消息給窗口程序。 } else { Direct3D_Update(hwnd,fTimeDelta); //調用更新函數,進行畫面的更新 Direct3D_Render(hwnd,fTimeDelta); //調用渲染函數,進行畫面的渲染 } } 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,0.0f); //調用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); // 設置光照 ::ZeroMemory(&g_Light, sizeof(g_Light)); g_Light.Type = D3DLIGHT_DIRECTIONAL; g_Light.Ambient = D3DXCOLOR(0.7f, 0.7f, 0.7f, 1.0f); g_Light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); g_Light.Specular = D3DXCOLOR(0.9f, 0.9f, 0.9f, 1.0f); g_Light.Direction = D3DXVECTOR3(1.0f, 1.0f, 1.0f); g_pd3dDevice->SetLight(0, &g_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, 1400.0f, -1800.0f)); //設置攝像機所在的位置 g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 1500.0f, 0.0f)); //設置目標觀察點所在的位置 g_pCamera->SetViewMatrix(); //設置取景變換矩陣 g_pCamera->SetProjMatrix(); //設置投影變換矩陣 // 創建并初始化地形 g_pTerrain = new TerrainClass(g_pd3dDevice); g_pTerrain->LoadTerrainFromFile(L"GameMedia\\heighmap.raw", L"GameMedia\\terrainstone.jpg"); //從文件加載高度圖和紋理 g_pTerrain->InitTerrain(200, 200, 60.0f, 8.0f); //四個值分別是頂點行數,頂點列數,頂點間間距,縮放系數 //創建并初始化天空對象 g_pSkyBox = new SkyBoxClass( g_pd3dDevice ); g_pSkyBox->LoadSkyTextureFromFile(L"GameMedia\\SunSetFront2048.png",L"GameMedia\\SunSetBack2048.png",L"GameMedia\\SunSetRight2048.png",L"GameMedia\\SunSetLeft2048.png", L"GameMedia\\SunSetUp2048.png");//從文件加載前、后、左、右、頂面5個面的紋理圖 g_pSkyBox->InitSkyBox(50000); //設置天空盒的邊長 //創建并初始化雪花粒子系統 g_pSnowParticles = new SnowParticleClass(g_pd3dDevice); g_pSnowParticles->InitSnowParticle(); HRESULT hr; //載入第一個模型 g_pXFileModel1 = new XFileModelClass(g_pd3dDevice); HR( g_pXFileModel1->LoadModelFromXFile(L"knight.X" )); //載入第二個模型 g_pXFileModel2 = new XFileModelClass(g_pd3dDevice); HR( g_pXFileModel2->LoadModelFromXFile(L"dragon.X" )); //載入第三個模型 g_pXFileModel3 = new XFileModelClass(g_pd3dDevice); HR( g_pXFileModel3->LoadModelFromXFile(L"Demon.X" )); return S_OK; } void Direct3D_Update( HWND hwnd,FLOAT fTimeDelta) { //使用DirectInput類讀取數據 g_pDInput->GetInput(); // 沿攝像機各分量移動視角 if (g_pDInput->IsKeyDown(DIK_A)) g_pCamera->MoveAlongRightVec(-1.0f); if (g_pDInput->IsKeyDown(DIK_D)) g_pCamera->MoveAlongRightVec( 1.0f); if (g_pDInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 1.0f); if (g_pDInput->IsKeyDown(DIK_S)) g_pCamera->MoveAlongLookVec(-1.0f); if (g_pDInput->IsKeyDown(DIK_R)) g_pCamera->MoveAlongUpVec( 1.0f); if (g_pDInput->IsKeyDown(DIK_F)) g_pCamera->MoveAlongUpVec(-1.0f); //沿攝像機各分量旋轉視角 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_Q)) g_pCamera->RotationLookVec(0.001f); if (g_pDInput->IsKeyDown(DIK_E)) g_pCamera->RotationLookVec( -0.001f); //鼠標控制右向量和上向量的旋轉 g_pCamera->RotationUpVec(g_pDInput->MouseDX()* 0.0006f); g_pCamera->RotationRightVec(g_pDInput->MouseDY() * 0.0006f); //鼠標滾輪控制觀察點收縮操作 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); ShowCursor(false); //隱藏鼠標光標 } //***************************************************************************************** // Name: Direct3D_Render() // Desc: 進行圖形的渲染操作 // Point:【Direct3D渲染五步曲】 // 1.渲染五步曲之一,清屏操作 // 2.渲染五步曲之二,開始繪制 // 3.渲染五步曲之三,正式繪制 // 4.渲染五步曲之四,結束繪制 // 5.渲染五步曲之五,翻轉顯示 //***************************************************************************************** void Direct3D_Render(HWND hwnd,FLOAT fTimeDelta) { //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之一】:清屏操作 //-------------------------------------------------------------------------------------- g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100, 255, 255), 1.0f, 0); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之二】:開始繪制 //-------------------------------------------------------------------------------------- g_pd3dDevice->BeginScene(); // 開始繪制 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之三】:正式繪制 //-------------------------------------------------------------------------------------- //以下這段代碼用于繪制游戲角色 D3DXMATRIX mScal,mTrans1,mTrans2,mTrans3,mFinal1,mFinal2,mFinal3; //定義一些矩陣,準備對模型進行矩陣變換 //第一個角色的繪制,首先是“調合”出這個角色合適的世界矩陣,然后再繪制 D3DXMatrixTranslation(&mTrans1,50.0f,1200.0f,0.0f); D3DXMatrixScaling(&mScal,3.0f,3.0f,3.0f); mFinal1=mScal*mTrans1*g_matWorld; g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal1);//設置模型的世界矩陣,為繪制做準備 g_pXFileModel1->RenderModel( ); //第二個角色的繪制,首先是“調合”出這個角色合適的世界矩陣,然后再繪制 D3DXMatrixTranslation(&mTrans2, 200.0f, 0.0f, 0.0f); mFinal2=mTrans2*mFinal1; g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal2); g_pXFileModel2->RenderModel( ); //第三個角色的繪制,首先是“調合”出這個角色合適的世界矩陣,然后再繪制 D3DXMatrixTranslation(&mTrans3, -200.0f, 0.0f, 0.0f); mFinal3=mTrans3*mFinal1; g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal3); g_pXFileModel3->RenderModel( ); //繪制地形 g_pTerrain->RenderTerrain(&g_matWorld, false); //渲染地形,且第二個參數設為false,表示不渲染出地形的線框 //繪制天空 D3DXMATRIX matSky,matTransSky,matRotSky; D3DXMatrixTranslation(&matTransSky,0.0f,-12000.0f,0.0f); D3DXMatrixRotationY(&matRotSky, -0.00005f*timeGetTime()); //旋轉天空網格, 簡單模擬云彩運動效果 matSky=matTransSky*matRotSky; g_pSkyBox->RenderSkyBox(&matSky, false); //繪制雪花粒子系統 g_pSnowParticles->UpdateSnowParticle(fTimeDelta); g_pSnowParticles->RenderSnowParticle(); //繪制文字信息 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" R:垂直向上飛翔 F:垂直向下飛翔", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" Q:向左傾斜 E:向右傾斜", -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接口對象 SAFE_DELETE(g_pDInput); SAFE_RELEASE(g_pd3dDevice); SAFE_RELEASE(g_pTextAdaperName) SAFE_RELEASE(g_pTextHelper) SAFE_RELEASE(g_pTextInfor) SAFE_RELEASE(g_pTextFPS) SAFE_RELEASE(g_pd3dDevice) }
這次的游戲場景有些西方風格的“暗黑”化,天空盒的紋理素材選擇的是深色的傍晚,然后3個模型的選擇之前已經說了,非常有代表性,來自地獄代表隊的地獄惡魔一頭,來自人類代表隊的人族騎士一個,來自天堂代表隊的天堂雷龍一只。
我們來看一下運行截圖:
首先是縱覽圖一張:
然后三張模型的正面圖:
然后三張側面圖:
再來一張縱覽:
特寫鏡頭一張:劃破天際的鋒利刀刃
文章最后,依舊是放出本篇文章配套源代碼的下載:
本節筆記配套源代碼請點擊這里下載:
【淺墨DirectX提高班】配套源代碼之十九下載
以上就是本節筆記的全部內容,更多精彩內容,且聽下回分解。
淺墨在這里,希望喜歡游戲開發系列文章的朋友們能留下你們的評論,每次淺墨登陸博客看到大家的留言的時候都會非常開心,感覺自己正在傳遞一種信仰,一種精神。
文章最后,依然是【每文一語】欄目,今天的句子是:
成功的人都是從逆境中堅持下來的,我們應感謝生命賜予的不幸和挫折,讓我們證明我們的堅強和勇敢。
下周一,讓我們離游戲開發的夢想更近一步。
下周一,游戲開發筆記,我們,不見不散。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。