第24章 让唯美的雪花飘扬——三维粒子系统的实现
本章我們將一起探討三維游戲中粒子系統的方方面面, 首先對粒子系統的基本概念特性做一個全面的認知,然后依舊是把粒子系統封裝在一個C++類中,模擬了三維游戲中唯美的雪花飛揚的景象, 讓我們之前實現的綜合三維游戲場景更加炫。
大家應該記得,我們之前也用GDI 實現過雪花粒子系統,那個時候由于圖形庫GDI的限制,實現效果或多或少顯得有些拙劣,本章我們在DirectX 的幫助下,專門用粒子系統重新實現了唯美雪花的飛揚景象,算是為強大的粒子系統正名吧。
24.1 對粒子系統的基本認知
1983 年,奇才Reeves .V.T 在他發表的論文《Particle Systems A Technique for Modeling a Class of? Fuzzy Objects》中首次提出了粒子系統的概念。從此,粒子系統就開始廣泛運用于計算機中各種模 糊景物的模擬。經常使用粒子系統模擬的現象有火焰、爆炸、煙、水流、火花、落葉、云、霧、雪、 塵、流星尾跡或者像發光軌跡這樣的抽象視覺效果等等。這些物體模型在計算機中往往很難用具體 的形狀、大小來描述,但是我們可以通過粒子系統的思想,去描述組成這些物體的每個元素和它的 變化。一般情況下,粒子的幾何特征都十分簡單,可以采用一個像素或者一個小的多邊形來表示。需 要注意的是,粒子系統的最大的缺陷是,當粒子數量達到很大的規模的時候, 對運行時機器性能的 要求會更加苛刻,如果機器的性能跟不上, 就會達不到實時的顯示效果,換句話說,就是粒子太多 了,我們的電腦跑不動了, 很卡。
在許多三維建模及渲染包內部就可以創建、修改粒子系統,如3DS Max 、Maya 以及Blender 等,這些編輯程序使藝術家能夠立即看到它們設定的特性或者規則下粒子系統的表現。 ?而2D 的粒子 特效軟件中, particlelllusion 最為出色, 因為他的渲染比一般的3D 軟件快較為平面化。 目前,粒子系統技術被廣泛用于大型3D 游戲的制作中。
下圖是DirectX SDK 中自帶Sample 一個和粒子系統相關的非常華麗的demo ,推薦大家試著運行一下,如果你的DirectX SDK 安裝在D 盤,那么路徑就是:
D:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Samples\C++\Direct3D11\NBodyGravityCS11
放2張運行的截圖:
??
上面程序中的NBody 粒子系統不是普通的粒子系統,它運用了并行計算,以模擬銀河系的強大級粒子系統。普通粒子系統的粒子之間是無關聯的. 而NBody 的粒子之間有引力的影響存在,這點大家需要注意。
粒子系統通常有3 個要素:群體性、統一性和隨機性。下面我們簡單看一下它們的定義:
- ?群體性:粒子系統是由大量的可見元素構成的。因此,用粒子系統描述一團煙霧是合情合理的,但是我們所用粒子系統去描述一粒煙霧顯然只有鬧笑話了。
- ?統一性:粒子系統的每個元素有具有相同的表現規律。比如,雪花粒子系統中的每一片雪花,都是白色元暇、輕盈靈動的。如果雪花粒子系統中出現了彩色的粒子,那顯然就是異類了。
- 隨機性: 粒子系統中每個元素會隨機表現出不同的特性。比如煙霧中每一個煙霧粒子的運動軌跡往往都是雜亂無章的,但是對于整個煙霧系統來說,這些煙霧粒子往往都有一個大體的運動方向。
24.2 粒子系統的基本原理
粒子通常都是一個帶有紋理的四邊形,通常是使用了紋理映射的四邊形??梢哉J為粒子實際上 是一個很小的網格模型,只不過是紋理賦予了它特殊的外表罷了。繪制粒子就如同繪制多邊形一樣 簡單,因為一個粒子說白了就是一個可改變大小并映射了紋理的四邊形罷了。下圖所示的就是一個20 個單位大小的粒子。
如果給出了粒子中心點的坐標和粒子的大小,不難計算出繪制粒子所需要的4 個頂點坐標,這樣往往比直接給4 個頂點坐標來得直觀和節省空間。
另外,很多情況下,因為一個例子是使用兩個三角形組成的一個矩形來表示的,所以通常需要使粒子四邊形始終面向觀察者,這就用到了Direct3D 中的廣告板(Billboard )技術,也叫公告版技術。公告版技術的基本原理在這里也提一下吧,后面有機會就專門講解一下。公告版技術的基本原理就是在渲染一個多邊形時,首先根據觀察方向構造一個旋轉矩陣,利用這個旋轉矩陣旋轉多邊形,讓這個多邊形始終是面向觀察者的,如果觀察方向是不斷變化的, 那么我們這個旋轉矩陣也要不斷進行調節。這樣,我們始終看到的是這個多邊形“最美好”的一面。這種先讓多邊形面向觀察者,然后再渲染的技術,就是傳說中的廣告板技術。
我們知道,粒子系統都由大量的粒子構成,每個粒子都有一組屬性,如位置、大小以及紋理,還比如顏色、透明度、運動速度、加速度、自旋周期、生命周期等等屬性。一個粒子需要具有什么樣的屬性, 當然是取決于具體的運用了。
另外,粒子屬性的初始值常常都是隨機值,而粒子的產生也常常是由位于空間中某個位置的粒子源產生的。
粒子系統在宏觀和微觀上都是隨時間不斷變化的, 一套粒子系統在它生命周期的每一刻, 一般都需完成以下的四步曲的工作:
1 . 產生新的粒子
在這一步中,我們會根據預定的要求, 產生一定數目的新粒子。粒子的各項初始屬性都可以用rand 函數來在一定的范圍內賦上隨機值。
2. 更新現有粒子的屬性
比如粒子有位置和移動速度, 自旋速度等等屬性,這就需要在每一幀當中根據原來的粒子的位置、移動速度和自旋速度重新進行計算和賦值更新。
3. 刪除已經消亡的粒子
這一步是可選的, 具體情況具體分析,因為有些粒子系統中粒子是一直都存在的,沒有消亡一說。在規定了粒子生命周期的一套粒子系統中,需要判斷每個粒子是否生命走到了盡頭,如果是的話,那么它就消亡了,得用相關代碼把它從粒子系統中消除。
4. 繪制出粒子
這步沒有的話什么都不是,不顯示出來叫什么粒子系統啊。人家可不管你在之前做了多少工作,算了多少東西,反正玩家是要看到最終的顯示效果的。
在Direct3D 8.0 以后,我們可以通過一種稱為點精靈( Point Sprite )的特殊點元來描述粒子系統中的粒子。和一般點元不同的是,點精靈可以進行紋理映射并改變大小。點精靈的使用常常是伴隨著SetRenderState 中第一個參數取如下的幾個值:
D3DRS POINTS I ZE = 154 ,D3DRS POINTSIZE MIN = 155,D3DRS POINTSPRITEENABLE = 156,D3DRS POINTSCALEENABLE = 157 ,D3DRS POINTSCALE_A = 158 ,D3DRS POINTSCALE_B = 159,D3DRS POINTSCALE_C = 160 , 另外,粒子系統中的一個重要要素是保存粒子的存儲結構。我們可以使用數組,如果需要動態 插入和刪除原始的話,就進一步使用鏈表或者模板了。
需要注意的是,因為粒子系統中會有很多粒子需要不斷地產生、消亡,如果在每個粒子產生時都分配內存,或者在每個粒子消亡時都釋放內存,這顯然會造成巨大的資源開銷,非常不推薦。這里我們按鏈表這種方案來講解。通常采用的做法是,未雨綢繆,預先為所有的粒子分配內存,并將這些粒子保存到一個鏈表當中。當需要產生新的粒子時,從這個鏈表中取出所需數量的粒子,并將
它們加入到渲染鏈表中,而當一個粒子消亡后,重新將它們放回到原鏈表中,并從渲染鏈表中刪除這些粒子。最后,在程序結束時,一次性釋放所有粒子所占的內存空間。這就是比較科學的做法。
24.3 雪花粒子系統的設計
我們之前己經提到過,粒子系統可以模擬很多的現象,比如火焰、爆炸、煙、水流、火花、落葉、云、霧、雪、塵、流星尾跡或者發光軌跡。對于現象的模擬,粒子的特性往往需要根據模擬的現象的屬性來具體地設計。對于本節需要使用粒子系統來模擬的雪花飛揚場景,有兩個比較特殊的地方:
- ?在雪花飛揚場景中,不需要用點精靈或者公告版技術來讓粒子的4 個頂點所在的面始終朝向觀察者,因為雪花飛舞起來是非常優雅的,會悠揚地繞著不同的軸打轉,用了公告板技術反而畫蛇添足,顯得不那么真實了。
- ?在雪花飛揚場景中,不需要粒子的動態消亡與產生,可以讓雪花粒子在一定區域內下落,如果下落到Y 軸的臨界區域, 就把粒子的Y 坐標設為預定的臨界最高點的Y坐標,就像粒子都是從這個地方產生的一樣,這樣就會模擬出源源不斷地下雪景象。
依舊是寫出這個名為SnowParticleClass 類的大體輪廓。
首先當頭棒喝怒寫4 個宏,方便宏觀調控。這四個宏分別用于表示雪花粒子數量, 雪花飛揚區域的長度, 雪花飛揚區域的寬度, 雪花飛揚區域的高度。
#define PARTICLE_NUMBER 100000 //雪花粒子數量,顯卡不好、運行起來卡的童鞋請取小一點。 #define SNOW_SYSTEM_LENGTH_X 20000 //雪花飛揚區域的長度 #define SNOW_SYSTEM_WIDTH_Z 20000 //雪花飛揚區域的寬度 #define SNOW_SYSTEM_HEIGHT_Y 20000 //雪花飛揚區域的高度 這里的PARTICLE_NUMBER 雪花粒子數量我們取了10 萬,那么后面我們寫出來的游戲場景中就有10 萬個雪花粒子, 這個前提是你的顯卡禁得住10 萬及以上的粒子數量。當然,你取20 萬的粒子數量,在下雪區域是20000 × 20000 × 20000 的區域中就是超級大暴雪了……建議這時候把長度和寬度調大一些,來讓這20 萬的粒子的活動區域更大。
接下來我們寫出雪花粒子的FVF 靈活頂點格式,頂點屬性是頂點坐標加上紋理坐標了:
//------------------------------------------------------------------------------------------------- //雪花粒子的FVF頂點結構和頂點格式 //------------------------------------------------------------------------------------------------- struct POINTVERTEX {float x, y, z; //頂點位置float u,v ; //頂點紋理坐標 }; #define D3DFVF_POINTVERTEX (D3DFVF_XYZ|D3DFVF_TEX1) 再接下來是雪花粒子的屬性結構體,想一想現實生活中的雪花有哪些特定的屬性呢?唯美的雪花,有特定的位置, 會旋轉,有下降速度,樣子不同,嗯,好,那我們就這樣寫: //------------------------------------------------------------------------------------------------- // Desc: 雪花粒子結構體的定義 //------------------------------------------------------------------------------------------------- struct SNOWPARTICLE {float x, y, z; //坐標位置float RotationY; //雪花繞自身Y軸旋轉角度float RotationX; //雪花繞自身X軸旋轉角度float FallSpeed; //雪花下降速度float RotationSpeed; //雪花旋轉速度int TextureIndex; //紋理索引數 };好,邊角工作完成了,下面正式來設計這個類吧。首先來看一看需要哪些成員變量,LPDIRECT3DDEVICE9 類型的設備接口指針m_pd3dDevice 不能少吧,雪花粒子數組m_Snows 要有吧,頂點緩存對象m_pVertexBuffer 要有吧,保存不同雪花紋理樣式的雪花紋理數組m_pTexture要有吧,嗯,成員變量就這些。
然后看看要有哪些成員函數,構造函數、析構函數先顯式地寫出來,再是粒子系統初始化函數lnitSnowParticle,粒子系統更新函數UpdateSnowParticle , 粒子系統渲染函數RenderSnowParticle,嗯,成員函數也就是這些了。整體來看,這個類的輪廓就是下面SnowParticleClass.h 的全部代碼:
//============================================================================= // Name: SnowParticleClass.h // Des: 一個封裝了雪花粒子系統系統的類的頭文件 // 2013年 3月31日 Create by 淺墨 //=============================================================================#pragma once #include "D3DUtil.h" #define PARTICLE_NUMBER 15000 //雪花粒子數量,顯卡不好、運行起來卡的童鞋請取小一點。 #define SNOW_SYSTEM_LENGTH_X 20000 //雪花飛揚區域的長度 #define SNOW_SYSTEM_WIDTH_Z 20000 //雪花飛揚區域的寬度 #define SNOW_SYSTEM_HEIGHT_Y 20000 //雪花飛揚區域的高度//------------------------------------------------------------------------------------------------- //雪花粒子的FVF頂點結構和頂點格式 //------------------------------------------------------------------------------------------------- struct POINTVERTEX {float x, y, z; //頂點位置float u,v ; //頂點紋理坐標 }; #define D3DFVF_POINTVERTEX (D3DFVF_XYZ|D3DFVF_TEX1)//------------------------------------------------------------------------------------------------- // Desc: 雪花粒子結構體的定義 //------------------------------------------------------------------------------------------------- struct SNOWPARTICLE {float x, y, z; //坐標位置float RotationY; //雪花繞自身Y軸旋轉角度float RotationX; //雪花繞自身X軸旋轉角度float FallSpeed; //雪花下降速度float RotationSpeed; //雪花旋轉速度int TextureIndex; //紋理索引數 };//------------------------------------------------------------------------------------------------- // Desc: 粒子系統類的定義 //------------------------------------------------------------------------------------------------- class SnowParticleClass { private:LPDIRECT3DDEVICE9 m_pd3dDevice; //D3D設備對象SNOWPARTICLE m_Snows[PARTICLE_NUMBER]; //雪花粒子數組LPDIRECT3DVERTEXBUFFER9 m_pVertexBuffer; //粒子頂點緩存LPDIRECT3DTEXTURE9 m_pTexture[6]; //雪花紋理數組public:SnowParticleClass(LPDIRECT3DDEVICE9 pd3dDevice); //構造函數~SnowParticleClass(); //析構函數HRESULT InitSnowParticle(); //粒子系統初始化函數HRESULT UpdateSnowParticle( float fElapsedTime); //粒子系統更新函數HRESULT RenderSnowParticle( ); //粒子系統渲染函數 };
24.4 雪花粒子系統的實現
又到了做填空題的時候,對著上面我們勾勒出來的SnowParticleClass 類,有5 個函數需要填上實現代碼:首先呢,構造函數:
//------------------------------------------------------------------------------------------------- // Desc: 構造函數 //------------------------------------------------------------------------------------------------- SnowParticleClass::SnowParticleClass(LPDIRECT3DDEVICE9 pd3dDevice) {//給各個參數賦初值m_pd3dDevice=pd3dDevice;m_pVertexBuffer=NULL; for(int i=0; i<5; i++)m_pTexture[i] = NULL; }接下來, 粒子系統初始化函數lnitSnowParticle() 。首先, 調用srand 重新播種一下隨機數種子。然后for 循環為所有的雪花粒子賦予獨一無二的各項屬性值。接著,用講爛了的頂點緩存使用五步曲的其中三步為代表著所有雪花粒子屬性的一個頂點緩存賦值,最后調用6 次D3DXCreateTextureFromFile 從文件加載6 種不同的雪花紋理邊來。這6 種雪花紋理圖是按照素材PS 出來,分別導出的,效果圖在下面,它們還不錯,各有特點, 非常漂亮:
按照上面的思考, InitSnowParticle()函數的實現代碼我們就知道怎么寫了:
//------------------------------------------------------------------------------------------------- // Name: SnowParticleClass::InitSnowParticle( ) // Desc: 粒子系統初始化函數 //------------------------------------------------------------------------------------------------- HRESULT SnowParticleClass::InitSnowParticle( ) {//初始化雪花粒子數組srand(GetTickCount());for(int i=0; i<PARTICLE_NUMBER; i++){ m_Snows[i].x = float(rand()%SNOW_SYSTEM_LENGTH_X-SNOW_SYSTEM_LENGTH_X/2);m_Snows[i].z = float(rand()%SNOW_SYSTEM_WIDTH_Z-SNOW_SYSTEM_WIDTH_Z/2);m_Snows[i].y = float(rand()%SNOW_SYSTEM_HEIGHT_Y);m_Snows[i].RotationY = (rand()%100)/50.0f*D3DX_PI;m_Snows[i].RotationX = (rand()%100)/50.0f*D3DX_PI;m_Snows[i].FallSpeed = 300.0f + rand()%500;m_Snows[i].RotationSpeed = 5.0f + rand()%10/10.0f;m_Snows[i].TextureIndex = rand()%6;}//創建雪花粒子頂點緩存m_pd3dDevice->CreateVertexBuffer( 4*sizeof(POINTVERTEX), 0, D3DFVF_POINTVERTEX,D3DPOOL_MANAGED, &m_pVertexBuffer, NULL );//填充雪花粒子頂點緩存POINTVERTEX vertices[] ={{ -30.0f, 0.0f, 0.0f, 0.0f, 1.0f, },{ -30.0f, 60.0f, 0.0f, 0.0f, 0.0f, },{ 30.0f, 0.0f, 0.0f, 1.0f, 1.0f, }, { 30.0f, 60.0f, 0.0f, 1.0f, 0.0f, }};//加鎖VOID* pVertices;m_pVertexBuffer->Lock( 0, sizeof(vertices), (void**)&pVertices, 0 );//訪問memcpy( pVertices, vertices, sizeof(vertices) );//解鎖m_pVertexBuffer->Unlock();//創建6種雪花紋理D3DXCreateTextureFromFile( m_pd3dDevice, L"GameMedia\\snow1.jpg", &m_pTexture[0] );D3DXCreateTextureFromFile( m_pd3dDevice, L"GameMedia\\snow2.jpg", &m_pTexture[1] );D3DXCreateTextureFromFile( m_pd3dDevice, L"GameMedia\\snow3.jpg", &m_pTexture[2] );D3DXCreateTextureFromFile( m_pd3dDevice, L"GameMedia\\snow4.jpg", &m_pTexture[3] );D3DXCreateTextureFromFile( m_pd3dDevice, L"GameMedia\\snow5.jpg", &m_pTexture[4] );D3DXCreateTextureFromFile( m_pd3dDevice, L"GameMedia\\snow6.jpg", &m_pTexture[5] );return S_OK; }接著我們來看一下粒子系統更新函數UpdateSnowParticle 怎么實現。其實非常簡單,就是一個for 循環遍歷所有的粒子,看有哪些需要更新的就可以了。對于這個雪花粒子系統, 需要更新一下每個粒子的Y 坐標, 判斷是否到了“地面”,然后還要改變其自旋角度。據此,代碼寫出來就是這樣了: //------------------------------------------------------------------------------------------------- // Name: SnowParticleClass::UpdateSnowParticle( ) // Desc: 粒子系統更新函數 //------------------------------------------------------------------------------------------------- HRESULT SnowParticleClass::UpdateSnowParticle( float fElapsedTime) {//一個for循環,更新每個雪花粒子的當前位置和角度for(int i=0; i<PARTICLE_NUMBER; i++){m_Snows[i].y -= m_Snows[i].FallSpeed*fElapsedTime;//如果雪花粒子落到地面, 重新將其高度設置為最大if(m_Snows[i].y<0)m_Snows[i].y = SNOW_SYSTEM_WIDTH_Z;//更改自旋角度m_Snows[i].RotationY += m_Snows[i].RotationSpeed * fElapsedTime;m_Snows[i].RotationX += m_Snows[i].RotationSpeed * fElapsedTime;}return S_OK; }最后來看一下最關鍵的粒子系統渲染函數RenderSnowParticle 怎么寫。首先禁用照明,然后設置紋理狀態,接著設置Alpha 混合系數,設置背面消隱模式為不剔除,最后就開始渲染。需要注意的是,設置Alpha 混合系數,之前沒有專門講解過,這里簡單理解它的功能為進行透明貼圖就行了,就是我們在寫GDI 游戲小程序的時候一直在做的糾結事情,把圖片背景的黑邊去掉。好了,思路有了,寫代碼還會難嗎? //------------------------------------------------------------------------------------------------- // Name: SnowParticleClass::RenderSnowParticle( ) // Desc: 粒子系統渲染函數 //------------------------------------------------------------------------------------------------- HRESULT SnowParticleClass::RenderSnowParticle( ) {//禁用照明效果m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false );//設置紋理狀態m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); //將紋理顏色混合的第一個參數的顏色值用于輸出m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); //紋理顏色混合的第一個參數的值就取紋理顏色值m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); //縮小過濾狀態采用線性紋理過濾m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); //放大過濾狀態采用線性紋理過濾//設置Alpha混合系數m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true); //打開Alpha混合m_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); //源混合系數設為1m_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); //目標混合系數設為1//設置剔出模式為不剔除任何面m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );//渲染雪花for(int i=0; i<PARTICLE_NUMBER; i++){//構造并設置當前雪花粒子的世界矩陣static D3DXMATRIX matYaw, matPitch, matTrans, matWorld;D3DXMatrixRotationY(&matYaw, m_Snows[i].RotationY);D3DXMatrixRotationX(&matPitch, m_Snows[i].RotationX);D3DXMatrixTranslation(&matTrans, m_Snows[i].x, m_Snows[i].y, m_Snows[i].z);matWorld = matYaw * matPitch * matTrans;m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld);//渲染當前雪花粒子m_pd3dDevice->SetTexture( 0, m_pTexture[m_Snows[i].TextureIndex] ); //設置紋理m_pd3dDevice->SetStreamSource(0, m_pVertexBuffer, 0, sizeof(POINTVERTEX)); //把包含的幾何體信息的頂點緩存和渲染流水線相關聯 m_pd3dDevice->SetFVF(D3DFVF_POINTVERTEX); //設置FVF靈活頂點格式m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); //繪制}//恢復相關渲染狀態:Alpha混合 、剔除狀態、光照m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, true );return S_OK; }在寫渲染代碼的時候,如果繪制的物體很多,那么我們需要養成好習慣,狀態設了之后再渲染,渲染完成之后要還原渲染之前時的狀態。需要繪制幾部分內容,就這樣做幾次。I
析構函數就很簡單了,就是收拾殘局,看看有什么COM 接口要釋放的:
//------------------------------------------------------------------------------------------------- // Desc: 析構函數 //------------------------------------------------------------------------------------------------- SnowParticleClass::~SnowParticleClass() {SAFE_RELEASE(m_pVertexBuffer);for(int i=0;i<3; i++){SAFE_RELEASE(m_pTexture[i]);} }
24.5 雪花飛揚粒子類的使用
與類打交道封裝功能和我們經歷的生活一樣,也是一個先苦后甜的過程。
設計和實現這個類的時候或許是苦澀的,但是先苦后甜是必須的,寫完這個類之后,用起來非常地方便,只用幾行代碼, 一個唯美的雪花飛揚景象就加入到我們的游戲場景中了。
也就是如下的3 步:
1:首先,定義一個SnowParticleClass 類的全局指針實例:
SnowParticleClass* g_pSnowParticles = NULL; //雪花粒子系統的指針實例 2:然后,在初始化階段拿著雪花飛揚類的指針對象SnowParticleClass 到處“指”,創建并初始化粒子系統:
//創建并初始化雪花粒子系統g_pSnowParticles = new SnowParticleClass(g_pd3dDevice);g_pSnowParticles->InitSnowParticle(); 3:最后,就是在Render 函數中依然是拿著雪花類的指針對象g_pSnowParticles 先指一下UpdateSnowParticle 函數,更新粒子系統,然后再指一下RenderSnowParticle 函數,進行渲染。
//繪制雪花粒子系統g_pSnowParticles->UpdateSnowParticle(fTimeDelta);g_pSnowParticles->RenderSnowParticle();另外,需要注意上面更新粒子系統的UpdateSnowParticle 函數,我們用到了一個流逝時間參數fTimeDelta ,所以就需要把我們服役多年的消息循環改成如下含有流逝時間的更加先進的消息循環體系,然后讓Direct3D_Update 和Direct3D_Render 各增加一個代表流逝時間的fTimeDelta 參數。 //消息循環過程MSG msg = { 0 }; //初始化msgwhile( 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); //調用渲染函數,進行畫面的渲染 }}
24.6 示例程序D3Ddemo19
本節示例程序在之前的基礎上又增加了兩個文件,也就是實現雪花飛揚粒子系統類的源文件和 頭文件。全部文件數量增加到了12 個,它們的列表如下圖所示。
我們依舊只貼出最能體現思想的、提綱輩領的main.cpp 中的核心代碼,首先依然是我們的老朋友Object_Init()函數:
//-----------------------------------【Object_Init( )函數】-------------------------------------- // 描述:渲染資源初始化函數,在此函數中進行要被渲染的物體的資源的初始化 //-------------------------------------------------------------------------------------------------- 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"angle.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);// 設置光照 ::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, 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, 60.0f, 8.0f); //四個值分別是頂點行數,頂點列數,頂點間間距,縮放系數//創建并初始化天空對象g_pSkyBox = new SkyBoxClass( g_pd3dDevice );g_pSkyBox->LoadSkyTextureFromFile(L"GameMedia\\TropicalSunnyDayFront2048.png",L"GameMedia\\TropicalSunnyDayBack2048.png",L"GameMedia\\TropicalSunnyDayRight2048.png",L"GameMedia\\TropicalSunnyDayLeft2048.png", L"GameMedia\\TropicalSunnyDayUp2048.png");//從文件加載前、后、左、右、頂面5個面的紋理圖g_pSkyBox->InitSkyBox(50000); //設置天空盒的邊長//創建并初始化雪花粒子系統g_pSnowParticles = new SnowParticleClass(g_pd3dDevice);g_pSnowParticles->InitSnowParticle();return S_OK; }然后是我們的另外一個好朋友,Direct3D_Render()函數:
//-----------------------------------【Direct3D_Render( )函數】------------------------------- // 描述:使用Direct3D進行渲染 //-------------------------------------------------------------------------------------------------- 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,mRot2,mTrans,mFinal; //定義一些矩陣,準備對大黃蜂進行矩陣變換D3DXMatrixTranslation(&mTrans,50.0f,1200.0f,0.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); //繪制此部分}//繪制柱子D3DXMATRIX TransMatrix, RotMatrix, FinalMatrix;D3DXMatrixRotationX(&RotMatrix, -D3DX_PI * 0.5f);g_pd3dDevice->SetLight(0, &g_Light); g_pd3dDevice->SetMaterial(&g_MaterialCylinder);g_pd3dDevice->SetTexture(0, NULL);//設置此部分的紋理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);}//繪制地形g_pTerrain->RenderTerrain(&g_matWorld, false); //渲染地形,且第二個參數設為false,表示不渲染出地形的線框//繪制天空D3DXMATRIX matSky,matTransSky,matRotSky;D3DXMatrixTranslation(&matTransSky,0.0f,-12000.0f,0.0f);D3DXMatrixRotationY(&matRotSky, -0.000005f*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); // 翻轉與顯示}到目前為止,我們用所學的知識寫出來的游戲場景程序已經比較炫了, 一些運行截圖如下:
24.7 章節小憩
我們可以發現,其實Direct3D 的固定功能流水線學到目前為止,基礎知識也就那么多,頂點緩存、索引緩存、四大變換、紋理映射、網格、模板緩存等等,把它們其中的幾個合理地組合在一起運用一下就是新的知識,這就衍生出了我們最近三章中講到的地形、天空、粒子系統等等知識。
總結
以上是生活随笔為你收集整理的第24章 让唯美的雪花飘扬——三维粒子系统的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: APP自动化基础之元素定位
- 下一篇: 安装windows远程桌面服务器,如何安