您好,登錄后才能下訂單哦!
本系列文章由zhmxy555(毛星云)編寫,轉載請注明出處。
作者:毛星云(淺墨) 郵箱: happylifemxy@163.com
這篇文章里,淺墨準備跟大家一起探討一下三維天空的幾種實現方式,然后在幾種方式之中選擇最常用的一種進行重點突破,用一個C++類把這種三維天空的實現方式封裝起來。這樣以后要使用三維天空來輔助繪制某個游戲場景的話,準備好天空的紋理圖,然后簡單地敲幾行代碼,調用一下這個天空類中我們親手寫出來的函數就搞定了。先放一張程序截圖:
程序源碼在文章末尾有給出下載地址。
一、游戲行業所用編程語言的認知
上面講到了類的封裝,目前市面上的高性能三維游戲引擎其實就是在做這樣的工作,把各種功能封裝在一個個C++類中。淺墨經常收到懷揣游戲開發夢想的初學者們的郵件,詢問進行游戲開發到底學什么語言最合適。淺墨在這里集中跟大家講一下得了。
大家都知道,撇開C語言不談,C++在目前高級編程語言中執行效率和性能首屈一指。大家也知道,三維游戲的畫面渲染有著非常高的性能需求。就光這一條對性能的要求,什么C#,什么java等等,全都只有在一旁抹鼻子哭了。
所以,事實如此,現在市面上所有畫質精美的單機游戲作品(鬼泣5,上古卷軸5,刺客信條3,仙劍奇俠傳5前傳,古劍奇譚等等……),所有的大型網絡游戲(Dota2,英雄聯盟,魔獸世界,龍之谷,劍靈等等等……),所有高性能的三維游戲引擎(虛幻3,Unity3D,Cry Engine3等等……),以及一些高性能的2D游戲引擎(Cocos2d-x等等),都是用C++來開發的。
其實游戲引擎并沒有那么神秘,說白了也就是那么回事,用類封裝好功能的C++代碼而已。C++寫出來的游戲引擎自然能跨平臺。Unreal Engine3、Unity3D、Cocos2d-x等游戲引擎就是絕好的例子。
學好C++,你可以親手寫出Unity3D,親手寫出 Cocos2d-x,讓大家都叫你大神,大家都用你寫的游戲引擎做游戲,等著你什么時候心情好了更新一下給引擎加更多功能;而不是只會盲目跟風,今天大家說Unity3D火,就都去學Unity3D,明天大家說Cocos2d-x熱門,就來學學Cocos2d-x。你學游戲引擎,學的只是人家某引擎作者某C++大神按心情來定的函數調用方式,學的只是如何調用一些別人寫好的一些類,一些API函數。這樣在別人規定給你的一些rule中固步自封,大家覺得有技術含量么?
我們是時候該該想一想了,為什么現在全球范圍內優秀的三維引擎就是沒有我們國產引擎的影子。
所以,無論是哪個平臺,Windows也好,iOS也好,Android也罷,如果你真正想在游戲開發領域有所作為,混出個名堂,請學C++,請學計算機圖形學,請了解計算機圖形API(OpenGL或者DirectX),而不是在跟風某種“熱門”的游戲引擎的大潮中隨波逐流,在某種移動平臺的游戲開發中迷信某某引擎,樂不思蜀,固步自封。
咳,扯遠了,而且有些小憤青了,也原諒淺墨,淺墨沒有歧視其他編程語言的意思。淺墨只是想表達,無論是哪個平臺(Windows,Play Station,Xbox,Android,iOS,WindowsPhone, WUII),在三維或者高性能游戲開發領域,確實就是C++的天下。
如果大家對游戲引擎的概念不太了解,還請看淺墨寫過的關于游戲引擎的導論:
【Visual C++】游戲開發筆記三十五 站在巨人的肩膀上:游戲引擎導論
二、三維天空技術闡述
回到正題來吧,講今天的主題三維天空的實現。
不要看游戲世界中的天空好像是無邊無際的,其實我們都被騙了。
在計算機的三維世界中,三維天空的繪制肯定不可能像現實生活中的天空一樣,一望無際綿延無盡,往往是通過一種假象來現實的。這種假象與古代人所說的“天圓地方”有著異曲同工之妙。反正就是一個足夠大的容器一樣的東西把我們罩在里面,讓我們像井底之蛙一樣以為這就是整個世界,世界就這么大,天空就這么大。而這個足以罩住我們所置身的游戲世界的容器,可以是一個立方體,也可以是半球,甚至是一個足夠大的平面。
目前描述三維天空的技術主要包括三種類型:
1.平面型天空(Sky Plane),僅用一個平面放到玩家頭頂。這種方案太弱了,太容易被玩家們看穿,真實感太低,技術含量也太低。但是對于并不太注意遠景的場景,用天空平面也不失為一種辦法。在這種情況下,用純色的霧來覆蓋整個遠景,使得遠處充滿神秘,遮一下羞也效果湊合。
2.天空穹廬(Sky Dome),放到玩家頭頂上的是一個曲面,通常都會為一個半球。就像這樣:
這種方案其實真實性最強,但是不是目前使用最廣泛的方案,它涉及到天空無縫銜接的素材匱乏等的問題。
3.天空盒(Sky Box),即放到場景的是一個立方體。它是目前使用最廣泛的三維天空模擬技術,網絡上素材豐富,所以這次就用教大家用天空盒來模擬三維天空。天空盒經常是由24個頂點、六個面組成的立方體(或者直接從做好的X模型文件載入天空盒),并經常會隨著視點的移動而移動,來刻畫極遠處玩家無法達到位置的天空。
天空盒對于我們來說并不是困難的事情,但是真正要在游戲中使得天空“好看”,那么,還需要有著漂亮的天空紋理素材圖,可以在網上搜羅(下文有講如何搜索),也可以拜托給美工童鞋們。
另外,在高級一些的應用中,天空盒的紋理可能同時會用來生成Cube Map,并用之來做水面倒影、云影、反光等很眩的特效,大家先有一個這方面的概念就好。
三. 天空盒的設計
本篇文章的核心知識登場。
1,準備天空盒紋理素材
天空盒的紋理自然就是我們這個天空盒子立方體每個面的紋理了,至少5個面,最多6個面,因為底面處是我們所在的土地,是地形,也就不用渲染為天空了。
這5個面可以分別單獨成文件,像這樣:
這5張紋理需要滿足的條件是:按照規定的幾個面拼接起來,能構成一幅360度并包含頂部的無縫銜接的全景圖:
另外,有些游戲引擎設定了需要把5個面按某種方式連起來和成一幅圖來使用,就像這樣的天空盒素材:
互聯網上關于天空盒的紋理素材資源很豐富,大家google/百度就可以找打很多資源的下載點。
建議用關鍵 skybox texture或者skybox download來google。
另外,如果想原創天空盒紋理的話,可以用DirectX SDK 中自帶的DirectX Texture tool工具完成。
2 天空盒類的設計
好了,開始我們的本職工作,寫代碼吧。
我們今天的任務是寫一個封裝了天空盒渲染功能的類,我們給這個類取名為SkyBoxClass。
我們來看下這個類中有哪些內容。
最開始國際慣例,LPDIRECT3DDEVICE9類型的設備接口指針m_pd3dDevice自然不能少。
然后這個類中需要處理24個帶紋理坐標的頂點來構成一個立方體盒子,自然少不了FVF靈活頂點格式和一個DIRECT3DVERTEXBUFFER接口的指針。
接著還要有五個紋理對象,分別儲存5個面上的紋理圖,所以一個LPDIRECT3DTEXTURE9類型的m_pTexture[5]自然也少不了。最后,還需要定義一個float類型的m_Length表示天空盒的邊長。結構體和成員變量就是這些了,我們再來看一下需要有哪些成員函數。
首先構造函數析構函數我們寫出來,接著再寫三個函數就夠了,它們分別是初始化天空盒頂點的InitSkyBox函數,加載紋理的LoadSkyTextureFromFile函數,渲染天空盒的RenderSkyBox函數。
SkyBoxClass類的輪廓就是這樣了,那么把上面我們的思路實現成代碼就是如下,即貼出SkyBoxClass.h中全部代碼:
//============================================================================= // Name: SkyBoxClass.h // Des: 一個封裝了三維天空盒系統的類的頭文件 // 2013年 3月24日 Create by 淺墨 //============================================================================= #pragma once #include "D3DUtil.h" //為天空盒類定義一個FVF靈活頂點格式 struct SKYBOXVERTEX { float x,y,z; float u,v; }; #define D3DFVF_SKYBOX D3DFVF_XYZ|D3DFVF_TEX1 class SkyBoxClass { private: LPDIRECT3DDEVICE9 m_pd3dDevice; //D3D設備對象 LPDIRECT3DVERTEXBUFFER9 m_pVertexBuffer; //頂點緩存對象 LPDIRECT3DTEXTURE9 m_pTexture[5]; //5個紋理接口對象 float m_Length; //天空盒邊長 public: SkyBoxClass( LPDIRECT3DDEVICE9 pDevice ); //構造函數 virtual ~SkyBoxClass(void); //析構函數 public: BOOL InitSkyBox( float Length ); //初始化天空盒函數 BOOL LoadSkyTextureFromFile(wchar_t *pFrontTextureFile, wchar_t *pBackTextureFile,wchar_t *pLeftTextureFile, wchar_t *pRightTextureFile,wchar_t *pTopTextureFile); //從文件加載天空盒五個方向上的紋理 VOID RenderSkyBox( D3DXMATRIX *pMatWorld, BOOL bRenderFrame ); //渲染天空盒,根據第一個參數設定天空盒世界矩陣,第二個參數選擇是否渲染出線框 };
三、 天空盒類的實現
類的框架勾勒出來了,接下來就很簡單,分別在類的cpp文件中實現類成員函數就好了。
Ⅰ.構造函數的實現
首先是類構造函數,蠻簡單,直接對著看類定義中有哪些變量,分別賦初值就行。除了Direct3D設備對象賦值成通過函數形參傳進來的設備對象指針pDevice之外,其他的參數根據類型統統取NULL或者0.0f:
//------------------------------------------------------------------------------------------------- // Desc: 構造函數 //------------------------------------------------------------------------------------------------- SkyBoxClass::SkyBoxClass( LPDIRECT3DDEVICE9 pDevice ) { //給各個參數賦初值 m_pVertexBuffer=NULL; m_pd3dDevice=pDevice; for(int i=0; i<5; i++) m_pTexture[i] = NULL; m_Length = 0.0f; }
Ⅱ. 頂點初始化函數InitSkyBox的實現
接下來要實現的就是最關鍵的頂點初始化函數InitSkyBox。首先,通過形參把天空盒的邊長傳給代表邊長的成員函數m_Length。接著就是我們熟悉的頂點緩存使用四步曲的二、三兩步——創建頂點緩存、訪問頂點緩存了。
我們在紋理映射第一講中就給出了立方體表面貼紋理的24個頂點需要怎么寫,我們這里的思路基本和之前講的相同,而與D3D實現的普通立方體貼圖不同的一點是,大部分情況下我們視點都包容在天空盒內部,因此,天空盒的頂點順序應當是正好與我們之前講的普通立方體的頂點順序相反。所以,InitSkyBox函數的實現代碼就是這樣:
//------------------------------------------------------------------------------------------------- // Name: SkyBoxClass::InitSkyBox( ) // Desc: 天空盒初始化函數,頂點緩沖區的賦值 //------------------------------------------------------------------------------------------------- BOOL SkyBoxClass::InitSkyBox( float Length ) { m_Length=Length; //1.創建。創建頂點緩存 m_pd3dDevice->CreateVertexBuffer( 20 * sizeof(SKYBOXVERTEX), 0, D3DFVF_SKYBOX, D3DPOOL_MANAGED, &m_pVertexBuffer, 0 ); //用一個結構體把頂點數據先準備好 SKYBOXVERTEX vertices[] = { //前面的四個頂點 { -m_Length/2, 0.0f, m_Length/2, 0.0f, 1.0f, }, { -m_Length/2, m_Length/2, m_Length/2, 0.0f, 0.0f, }, { m_Length/2, 0.0f, m_Length/2, 1.0f, 1.0f, }, { m_Length/2, m_Length/2, m_Length/2, 1.0f, 0.0f, }, //背面的四個頂點 { m_Length/2, 0.0f, -m_Length/2, 0.0f, 1.0f, }, { m_Length/2, m_Length/2, -m_Length/2, 0.0f, 0.0f, }, { -m_Length/2, 0.0f, -m_Length/2, 1.0f, 1.0f, }, { -m_Length/2, m_Length/2, -m_Length/2, 1.0f, 0.0f, }, //左面的四個頂點 { -m_Length/2, 0.0f, -m_Length/2, 0.0f, 1.0f, }, { -m_Length/2, m_Length/2, -m_Length/2, 0.0f, 0.0f, }, { -m_Length/2, 0.0f, m_Length/2, 1.0f, 1.0f, }, { -m_Length/2, m_Length/2, m_Length/2, 1.0f, 0.0f, }, //右面的四個頂點 { m_Length/2, 0.0f, m_Length/2, 0.0f, 1.0f, }, { m_Length/2, m_Length/2, m_Length/2, 0.0f, 0.0f, }, { m_Length/2, 0.0f, -m_Length/2, 1.0f, 1.0f, }, { m_Length/2, m_Length/2, -m_Length/2, 1.0f, 0.0f, }, //上面的四個頂點 { m_Length/2, m_Length/2, -m_Length/2, 1.0f, 0.0f, }, { m_Length/2, m_Length/2, m_Length/2, 1.0f, 1.0f, }, { -m_Length/2, m_Length/2, -m_Length/2, 0.0f, 0.0f, }, { -m_Length/2, m_Length/2, m_Length/2, 0.0f, 1.0f, }, }; //準備填充頂點數據 void* pVertices; //2.加鎖 m_pVertexBuffer->Lock( 0, 0, (void**)&pVertices, 0 ); //3.訪問。把結構體中的數據直接拷到頂點緩沖區中 memcpy( pVertices, vertices, sizeof(vertices) ); //4.解鎖 m_pVertexBuffer->Unlock(); return TRUE; }
Ⅲ.紋理載入函數LoadSkyTextureFromFile的寫法
接下來看看紋理載入函數LoadSkyTextureFromFile的寫法,實在是非常非常簡單。
給5個文件路徑給他,傳進來調用5次D3DXCreateTextureFromFile函數載入紋理到m_pTexture[]數組中就好了:
//------------------------------------------------------------------------------------------------- // Name: SkyBoxClass::LoadSkyTextureFromFile( ) // Desc: 天空盒紋理加載函數 //------------------------------------------------------------------------------------------------- BOOL SkyBoxClass::LoadSkyTextureFromFile(wchar_t *pFrontTextureFile, wchar_t *pBackTextureFile,wchar_t *pLeftTextureFile, wchar_t *pRightTextureFile,wchar_t *pTopTextureFile) { //從文件加載五張紋理 D3DXCreateTextureFromFile( m_pd3dDevice , pFrontTextureFile, &m_pTexture[0] ); //前面 D3DXCreateTextureFromFile( m_pd3dDevice , pBackTextureFile, &m_pTexture[1] ); //后面 D3DXCreateTextureFromFile( m_pd3dDevice , pLeftTextureFile, &m_pTexture[2] ); //左面 D3DXCreateTextureFromFile( m_pd3dDevice , pRightTextureFile, &m_pTexture[3] ); //右面 D3DXCreateTextureFromFile( m_pd3dDevice , pTopTextureFile, &m_pTexture[4] ); //上面 return TRUE; }
Ⅳ. 渲染函數RenderSkyBox
再看看作用為渲染天空盒RenderSkyBox函數。其中我們用到了講解紋理映射的時候沒有講到的紋理階段混合操作,這里我們順便講一下。
紋理映射的本質實際上就是從紋理中獲取顏色值,然后應用到物體表面上。而以后我們會接觸到的多次紋理映射就是混合多層紋理的顏色,然后應用到物體表面。而為了處理上的方便,Direct3D將顏色的RGB通道和Alpha通道分開來進行處理,具體的操作方法就是通過紋理階段狀態(Texture Stage State)的設置。
其實也就是一個函數IDirect3DDevice9::SetTextureStageState的用法,在MSDN中查到這個函數原型如下:
HRESULT SetTextureStageState( [in] DWORD Stage, [in] D3DTEXTURESTAGESTATETYPE Type, [in] DWORD Value );
■ 第一個參數,DWORD類型的Stage,指定當前設置的紋理層為第幾層(有效值0~7)
■ 第二個參數,D3DTEXTURESTAGESTATETYPE類型的Type,填將要設置的紋理渲染狀態,在枚舉類型D3DTEXTURESTAGESTATETYPE中任意取值。先看完第三個參數,然后一起看一下這個D3DTEXTURESTAGESTATETYPE枚舉類型。
■ 第三個參數,DWORD類型的Value,表示所設置的狀態值,它是根據第二個參數來決定具體取什么值的。
下面就來一起看一下D3DTEXTURESTAGESTATETYPE枚舉類型的定義:
typedef enum D3DTEXTURESTAGESTATETYPE { D3DTSS_COLOROP = 1, D3DTSS_COLORARG1 = 2, D3DTSS_COLORARG2 = 3, D3DTSS_ALPHAOP = 4, D3DTSS_ALPHAARG1 = 5, D3DTSS_ALPHAARG2 = 6, D3DTSS_BUMPENVMAT00 = 7, D3DTSS_BUMPENVMAT01 = 8, D3DTSS_BUMPENVMAT10 = 9, D3DTSS_BUMPENVMAT11 = 10, D3DTSS_TEXCOORDINDEX = 11, D3DTSS_BUMPENVLSCALE = 22, D3DTSS_BUMPENVLOFFSET = 23, D3DTSS_TEXTURETRANSFORMFLAGS = 24, D3DTSS_COLORARG0 = 26, D3DTSS_ALPHAARG0 = 27, D3DTSS_RESULTARG = 28, D3DTSS_CONSTANT = 32, D3DTSS_FORCE_DWORD = 0x7fffffff } D3DTEXTURESTAGESTATETYPE, *LPD3DTEXTURESTAGESTATETYPE;
大家可以看到這個枚舉中的參數非常多,我們重點看一下前兩個參數。
■ D3DTSS_COLOROP:指定紋理顏色的混合方法,對應的Value值(SetTextureStageState第三個參數)在D3DTEXTUREOP枚舉類型中取值。我們把幾種常用的列出來就好了。Value值取D3DTOP_DISABLE表示禁用當前紋理層顏色輸出;Value值取D3DTOP_SELECTARG1或者D3DTOP_SELECTARG2,分別表示將顏色混合階段的第一個或者第二個參數的顏色值或者alpha值輸出。Value值取D3DTOP_MODULATE表示將顏色混合階層的第一個和第二個顏色相乘并輸出。
■ D3DTSS_COLORAG1:取這個值的話表示對紋理顏色混合階段的第一個參數進行操作,而它的Value值在D3DTA常量中取值,默認值為D3DTA_TEXTURE,表示這個紋理階段的參數就取紋理的顏色。
然后我們看一看RenderSkyBox函數中用到的兩句關于紋理階段狀態的代碼:
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); //將紋理顏色混合的第一個參數的顏色值用于輸出 m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); //紋理顏色混合的第一個參數的值就取紋理顏色值
第一句SetTextureStageState中我們表示要將紋理顏色混合的第一個參數的顏色值用于輸出,然后第二句馬上就把第一個參數的顏色值取為紋理顏色值了,這樣我們顏色混合后的值就是紋理的顏色值。
解決了紋理顏色混合的問題,后面就好解決了,設置世界矩陣,關聯頂點和渲染流水線,設置頂點格式,接著一個for循環設置紋理并渲染,最后再判斷一下是否要繪制出線框,一氣呵成。實現代碼就是這樣:
//-------------------------------------------------------------------------------------- // Name: SkyBoxClass::RenderSkyBox() // Desc: 繪制出天空盒,可以通過第二個參數選擇是否繪制出線框 //-------------------------------------------------------------------------------------- void SkyBoxClass::RenderSkyBox( D3DXMATRIX *pMatWorld, BOOL bRenderFrame ) { m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); //將紋理顏色混合的第一個參數的顏色值用于輸出 m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); //紋理顏色混合的第一個參數的值就取紋理顏色值 m_pd3dDevice->SetTransform( D3DTS_WORLD, pMatWorld ); //設置世界矩陣 m_pd3dDevice->SetStreamSource(0,m_pVertexBuffer, 0, sizeof(SKYBOXVERTEX)); //把包含的幾何體信息的頂點緩存和渲染流水線相關聯 m_pd3dDevice->SetFVF(D3DFVF_SKYBOX); //設置FVF靈活頂點格式 //一個for循環,將5個面繪制出來 for(int i =0; i<5; i++) { m_pd3dDevice->SetTexture(0, m_pTexture[i]); m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, i*4, 2); } //對是否渲染線框的處理代碼 if (bRenderFrame) //如果要渲染出線框的話 { m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME); //把填充模式設為線框填充 //一個for循環,將5個面的線框繪制出來 for(int i =0; i<5; i++) { m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, i*4, 2); //繪制頂點 } m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); //把填充模式調回實體填充 } }
Ⅴ.析構函數
最后再實現一下析構函數,看有什么COM接口對象,SAFE_RELEASE就行了:
//------------------------------------------------------------------------------------------------- // Desc: 析構函數 //------------------------------------------------------------------------------------------------- SkyBoxClass::~SkyBoxClass(void) { SAFE_RELEASE( m_pVertexBuffer ); for(int i=0; i<5; i++) { SAFE_RELEASE( m_pTexture[i] ); } }
這樣,一個封裝了天空盒的SkyBoxClass類就被我們實現出來了,可以看到,非常簡單,只需要填寫好六個面的24個頂點,最后為每個面貼上紋理就可以了。
四,天空盒類的使用
別看這個SkyBoxClass天空盒類寫起來還有些小麻煩,但是用起來非常方便。
Ⅰ.首先,定義一個SkyBoxClass類的指針:
SkyBoxClass* g_pSkyBox=NULL; //天空盒類的指針實例
Ⅱ.然后,在初始化階段拿著天空類的指針對象pSkyBox到處“指”,創建并初始化天空:
//創建并初始化天空對象 g_pSkyBox = new SkyBoxClass( g_pd3dDevice ); g_pSkyBox->LoadSkyTextureFromFile(L"GameMedia\\frontsnow1.jpg",L"GameMedia\\backsnow1.jpg",L"GameMedia\\leftsnow1.jpg",L"GameMedia\\rightsnow1.jpg", L"GameMedia\\topsnow1.jpg");//從文件加載前、后、左、右、頂面5個面的紋理圖 g_pSkyBox->InitSkyBox(20000); //設置天空盒的邊長
這里的GameMedia\\topsnow1.jpg表示在工程文件夾下的GameMedia文件夾中的topsnow1.jpg圖片。
Ⅲ.最后,就是在Render函數中依然是拿著天空類的指針對象pSkyBox指一下RenderSkyBox函數,進行渲染。
不過在渲染之前需要給RenderSkyBox函數準備一個合適的世界矩陣,我們這里為了把天空盒調到適當的地方先是創建了一個平移矩陣matTransSky,然后讓天空盒可以不停地緩慢移動,創建了一個隨系統時間隨Y軸旋轉的matRotSky矩陣。接著把這兩個矩陣相乘,結果等于最終的matSky矩陣,然后就可以把matSky作為參數,調用RenderSkyBox函數了。
//繪制天空 D3DXMATRIX matSky,matTransSky,matRotSky; D3DXMatrixTranslation(&matTransSky,0.0f,-3500.0f,0.0f); D3DXMatrixRotationY(&matRotSky, -0.000005f*timeGetTime()); //旋轉天空網格, 簡單模擬云彩運動效果 matSky=matTransSky*matRotSky; g_pSkyBox->RenderSkyBox(&matSky, false);
五、詳細注釋的源代碼欣賞
本篇文章配套的源代碼在之前的基礎上又增加了兩個文件,也就是實現天空類的源文件和頭文件。全部文件列表如下:
我們依舊只貼出核心代碼main.cpp,其他的眾多文件大家下源代碼回去看就好了。
//***************************************************************************************** // //【Visual C++】游戲開發筆記系列配套源碼四十九 淺墨DirectX教程十七 三維天空系統的實現 // VS2010版 // 2013年 3月24日 Create by 淺墨 // //***************************************************************************************** //***************************************************************************************** // Desc: 宏定義部分 //***************************************************************************************** #define SCREEN_WIDTH932//為窗口寬度定義的宏,以方便在此處修改窗口寬度 #define SCREEN_HEIGHT700//為窗口高度定義的宏,以方便在此處修改窗口高度 #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" //***************************************************************************************** // 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: 全局變量聲明部分 //***************************************************************************************** LPDIRECT3DDEVICE9g_pd3dDevice = NULL;//Direct3D設備對象 LPD3DXFONTg_pTextFPS =NULL; //字體COM接口 LPD3DXFONTg_pTextAdaperName = NULL; // 顯卡信息的2D文本 LPD3DXFONTg_pTextHelper = NULL; // 幫助信息的2D文本 LPD3DXFONTg_pTextInfor= NULL; // 繪制信息的2D文本 floatg_FPS= 0.0f; //一個浮點型的變量,代表幀速率 wchar_tg_strFPS[50] ={0}; //包含幀速率的字符數組 wchar_tg_strAdapterName[60] ={0}; //包含顯卡名稱的字符數組 D3DXMATRIXg_matWorld;//世界矩陣 LPD3DXMESHg_pMesh = NULL;// 網格對象 D3DMATERIAL9*g_pMaterials= NULL;// 網格的材質信息 LPDIRECT3DTEXTURE9*g_pTextures = NULL;// 網格的紋理信息 DWORDg_dwNumMtrls = 0;// 材質的數目 LPD3DXMESHg_cylinder = NULL;//柱子網格對象 D3DMATERIAL9g_MaterialCylinder;//柱子的材質 DInputClass*g_pDInput = NULL;//DInputClass類的指針實例 CameraClass*g_pCamera = NULL;//攝像機類的指針實例 TerrainClass*g_pTerrain = NULL;//地形類的指針實例 SkyBoxClass*g_pSkyBox=NULL; //天空盒類的指針實例 //***************************************************************************************** // Desc: 全局函數聲明部分 //***************************************************************************************** LRESULT CALLBACKWndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); HRESULTDirect3D_Init(HWND hwnd,HINSTANCE hInstance); HRESULTObjects_Init(); voidDirect3D_Render( HWND hwnd); voidDirect3D_Update( HWND hwnd); voidDirect3D_CleanUp( ); floatGet_FPS(); voidHelpText_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\\仙劍三·原版主題曲.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循環 { 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"95.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) //創建柱子 D3DXCreateCylinder(g_pd3dDevice, 280.0f, 10.0f, 3000.0f, 60, 60, &g_cylinder, 0); g_MaterialCylinder.Ambient = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); g_MaterialCylinder.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); g_MaterialCylinder.Specular = D3DXCOLOR(0.5f, 0.0f, 0.3f, 0.3f); g_MaterialCylinder.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 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.9f, 0.9f, 0.9f, 1.0f); light.Direction = D3DXVECTOR3(1.0f, 1.0f, 1.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, 1000.0f, -1200.0f)); //設置攝像機所在的位置 g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 1200.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, 30.0f, 6.0f); //四個值分別是頂點行數,頂點列數,頂點間間距,縮放系數 //創建并初始化天空對象 g_pSkyBox = new SkyBoxClass( g_pd3dDevice ); g_pSkyBox->LoadSkyTextureFromFile(L"GameMedia\\frontsnow1.jpg",L"GameMedia\\backsnow1.jpg",L"GameMedia\\leftsnow1.jpg",L"GameMedia\\rightsnow1.jpg", L"GameMedia\\topsnow1.jpg");//從文件加載前、后、左、右、頂面5個面的紋理圖 g_pSkyBox->InitSkyBox(20000); //設置天空盒的邊長 return S_OK; } voidDirect3D_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_R)) g_pCamera->MoveAlongUpVec( 0.3f); if (g_pDInput->IsKeyDown(DIK_F)) 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_Q)) g_pCamera->RotationLookVec(0.001f); if (g_pDInput->IsKeyDown(DIK_E)) 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); ShowCursor(false);//隱藏鼠標光標 } //***************************************************************************************** // 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(100, 255, 255), 1.0f, 0); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之二】:開始繪制 //-------------------------------------------------------------------------------------- g_pd3dDevice->BeginScene(); // 開始繪制 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之三】:正式繪制 //-------------------------------------------------------------------------------------- //繪制柱子 D3DXMATRIX TransMatrix, RotMatrix, FinalMatrix; D3DXMatrixRotationX(&RotMatrix, -D3DX_PI * 0.5f); g_pd3dDevice->SetMaterial(&g_MaterialCylinder); for(int i = 0; i < 4; i++) { D3DXMatrixTranslation(&TransMatrix, -300.0f, 0.0f, -350.0f + (i * 500.0f)); FinalMatrix = RotMatrix * TransMatrix ; g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix); g_cylinder->DrawSubset(0); D3DXMatrixTranslation(&TransMatrix, 300.0f, 0.0f, -350.0f + (i * 500.0f)); FinalMatrix = RotMatrix * TransMatrix ; g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix); g_cylinder->DrawSubset(0); } //繪制人物 D3DXMATRIX mScal,mRot2,mTrans,mFinal; //定義一些矩陣,準備對大黃蜂進行矩陣變換 D3DXMatrixTranslation(&mTrans,0.0f,600.0f,200.0f); D3DXMatrixScaling(&mScal,3.0f,3.0f,3.0f); mFinal=mScal*mTrans*g_matWorld; g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal);//設置模型的世界矩陣,為繪制做準備 // 用一個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_pTerrain->RenderTerrain(&g_matWorld, false); //渲染地形,且第二個參數設為false,表示不渲染出地形的線框 //繪制天空 D3DXMATRIX matSky,matTransSky,matRotSky; D3DXMatrixTranslation(&matTransSky,0.0f,-3500.0f,0.0f); D3DXMatrixRotationY(&matRotSky, -0.000005f*timeGetTime()); //旋轉天空網格, 簡單模擬云彩運動效果 matSky=matTransSky*matRotSky; g_pSkyBox->RenderSkyBox(&matSky, false); //繪制文字信息 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接口對象 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) }
然后放出一些截圖吧:
你做夢都不會發現,你所置身的“真實”的藍天白云,其實就是24個頂點加上一些貼圖罷了。
最后貼上揭露“天機”的鏡頭一張,大家其實就是在這個盒子中樂不思蜀的:
因為我們在這個程序中并沒有限定移動區域。所以是可以任意飛翔的,如果你毅力夠大(不去調相機的移動速度的話),完全可以飛啊飛,最后突出天空盒的包圍,識破天機。不過,這估計得“飛”個幾分鐘哦。
文章最后,依舊是放出本篇文章配套源代碼的下載:
本節筆記配套源代碼請點擊這里下載:
【淺墨DirectX提高班】配套源代碼之十七下載
以上就是本節筆記的全部內容,更多精彩內容,且聽下回分解。
淺墨在這里,希望喜歡游戲開發系列文章的朋友們能留下你們的評論,每次淺墨登陸博客看到大家的留言的時候都會非常開心,感覺自己正在傳遞一種信仰,一種精神。
文章最后,依然是【每文一語】欄目,今天的句子是:
迷茫時,堅定的對自己說,當時的夢想,我還記得。
下周一,讓我們離游戲開發的夢想更近一步。
下周一,游戲開發筆記,我們,不見不散。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。