【Visual C++】遊戲開發五十五 淺墨DirectX教程二十二 水乳交融的美學:alpha混合技術
本系列文章由zhmxy555(毛星雲)編寫,轉載請註明出處。
在這篇文章裡面,我們一起非常詳細地探討了Direct3D中Alpha混合相關的內容。首先是認識了Alpha通道與混合技術,然後結識了融合因子,瞭解了融合運算方式和融合因子的取法,以及Alpha的三處來源,接著是大家喜聞樂見的極易上手的使用三部曲,最後依舊是詳細註釋的程式原始碼的欣賞,程式截圖和每文一語欄目。
放截圖吧,不過為了不毀三觀,我們還是先放原版帶紋理的截圖:
對比圖,我們今天為了演示做出來的alpha效果圖:
說實話,這樣的人物模型用來做Alpha混合的演示有些凶殘,但是為了更好的掌握遊戲程式設計,我們豁出去了。:)
然後依舊是先和大家聊聊天,
昨天晚上去看了周杰倫摩天輪演唱會南京站,表示現場真是太震撼了。當杰倫唱起那些老歌比如《回到過去》,《晴天》的時候,讓淺墨想起了初中時代的那些無憂無慮的日子,發現時間真的是一去不復返了。
另外就是南京最近天氣忽然轉冷,淺墨一不小心中招感冒了,希望大家一定要注意防寒哈。
然後今天就是雙十一搶購日了,希望大家都能搶到自己心儀的寶貝。
好了,廢話不多說,我們進入正題吧。
一、初識Alpha通道與混合技術
大家應該都知道, Alpha通道是計算機中儲存一張圖片的透明和半透明度資訊的通道。它是一個8位的灰度通道,用256級灰度來記錄影象中的透明度資訊,定義透明、不透明和半透明區域,其中黑表示全透明,白表示不透明,灰表示半透明。
而混合,為Blending的英譯,是計算機圖形學中常用的一種技術,即混合畫素。我們通常用已經光柵化的畫素光柵化同一位置的畫素,或者說是在某圖元上混合圖元。這樣說不好理解的話,我們來舉個例子。說白了就是在一張圖元的地盤上又來了另一張圖元,然後他們按照我們指定的某種方式來像“揉麵團”一樣揉在一起,進行加工了再在原地顯示出來。
alpha混合技術對熟悉遊戲的朋友們來說應該不會陌生,這種技術在如今的遊戲特效裡已經被用爛了。且不說3D遊戲中它的頻繁登場,就算是2D的遊戲中,這種技術也是滿眼皆是。
alpha混合聽上去很神祕,實際非常簡單,其作用就是要實現一種半透明效果。假設一種不透明東西的顏色是A,另一種透明的東西的顏色是B,那麼透過B去看A,看上去的顏色C就是B和A的混合顏色,可以用這個式子來近似,設B物體的透明度為alpha(取值為0-1,0為完全透明,1為完全不透明)
R(C)=alpha*R(B)+(1-alpha)*R(A)
G(C)=alpha*G(B)+(1-alpha)*G(A)
B(C)=alpha*B(B)+(1-alpha)*B(A)
其中R(x)、G(x)、B(x)分別指顏色x的RGB分量(這裡自變數x取的是顏色C)。看起來這個東西這麼簡單,可是用它實現的效果確實非常的華麗,應用alpha混合技術,可以實現出最眩目的火光、煙霧、陰影、動態光源等等一切我們可以想象的出來的半透明效果。
二、Direct3D中的融合套路——融合因子
上面我們講到的是一般意義上的混合技術,而在Direct3D中,我們應該按如下的思路來進一步理解。
首先,Direct3D中依然是用Alpha通道來實現多個畫素顏色值的融合。每個畫素都包含四個分量:Alpha分量、紅色分量、綠色分量和藍色分量(即ARGB四分量)。其中,Alpha分量用於指定畫素的透明度,在0~255之間取值,0表示完全透明,255表示完全不透明。另外,根據使用的不同的顏色巨集的區別,還可能是在0.0~1.0之間取值。
在Direct3D中,融合這一領域有一個權威,那便是Alpha融合公式。Alpha融合公式如下:
其中RGB_src 和RGB_dst分別表示源畫素和目標畫素的顏色值,為包含四個顏色分量的顏色值。
K_src 和K_dst分別表示源融合因子和目標融合因子。他們指定了源畫素和目標畫素的顏色值在融合過程中所佔的比例,在[0,1]之間取值。通過原融合因子和目標融合因子,我們能夠以多種方式來修改源畫素和目標畫素的顏色值,從而獲得我們滿意的最終的融合後的顏色值。稍後會講解融合因子的具體取法,這裡我們先把這個融合公式解析完。
在融合公式中,OP表示源和目標的融合運算方式,由D3DBLENDOP列舉體來指定,需要注意的是它的預設值是源計算結果和目標計算結果相加。而運算子“∙”表示顏色值的每個分量都與和相乘。
三、融合運算方式的取法
上面我們剛提到過,融合運算方式由D3DBLENDOP列舉體來指定。
我們指的是SetRenderState中的第二個引數在D3DBLENDOP列舉體中取值,而第一個引數,取D3DRS_BLENDOP。
這一節就來看一下這個D3DBLENDOP列舉體的定義:
typedef enum D3DBLENDOP { D3DBLENDOP_ADD = 1, D3DBLENDOP_SUBTRACT = 2, D3DBLENDOP_REVSUBTRACT = 3, D3DBLENDOP_MIN = 4, D3DBLENDOP_MAX = 5, D3DBLENDOP_FORCE_DWORD = 0x7fffffff } D3DBLENDOP, *LPD3DBLENDOP;
我們用一個列表來進行講解吧:
D3DBLENDOP操作符 | 精析 |
D3DBLENDOP_ADD | 源畫素計算結果與目標畫素的計算結果相加,即【最終結果】=【源】+【目標】 |
D3DBLENDOP_SUBTRACT | 源畫素計算結果與目標畫素的計算結果相減,即【最終結果】=【源】-【目標】 |
D3DBLENDOP_REVSUBTRACT | 目標畫素的計算結果減去源畫素計算結果,即【最終結果】=【目標】-【源】 |
D3DBLENDOP_MIN | 在源畫素計算結果和目標畫素計算結果之間取小者。即【最終結果】= MIN(【目標】,【源】) |
D3DBLENDOP_MAX | 在源畫素計算結果和目標畫素計算結果之間取大者。即【最終結果】= MAX(【目標】,【源】) |
我們需要取什麼型別的融合運算方式,在表中查閱即可。再提醒大家一次,Direct3D中為我們預設取了融合運算方式為D3DBLENDOP_ADD,即源畫素計算結果與目標畫素的計算結果相加。
四、融合因子的取法
接著我們來看一下融合因子和的取法。源融合因子和目標融合因子可以在SetRenderState方法中第一個引數取D3DRS_SRCBLEND和D3DRS_DESTBLEND分別進行設定,而第二個引數都是在一個D3DBLEND列舉體中進行的取值,我們在MSDN中查到它的原型如下:
typedef enum D3DBLEND { D3DBLEND_ZERO = 1, D3DBLEND_ONE = 2, D3DBLEND_SRCCOLOR = 3, D3DBLEND_INVSRCCOLOR = 4, D3DBLEND_SRCALPHA = 5, D3DBLEND_INVSRCALPHA = 6, D3DBLEND_DESTALPHA = 7, D3DBLEND_INVDESTALPHA = 8, D3DBLEND_DESTCOLOR = 9, D3DBLEND_INVDESTCOLOR = 10, D3DBLEND_SRCALPHASAT = 11, D3DBLEND_BOTHSRCALPHA = 12, D3DBLEND_BOTHINVSRCALPHA = 13, D3DBLEND_BLENDFACTOR = 14, D3DBLEND_INVBLENDFACTOR = 15, D3DBLEND_SRCCOLOR2 = 16, D3DBLEND_INVSRCCOLOR2 = 17, D3DBLEND_FORCE_DWORD = 0x7fffffff } D3DBLEND, *LPD3DBLEND;
依舊是通過一個表格來對其中常用的引數進行講解,:
D3DBLEND融合型別 | 精析 |
D3DBLEND_ZERO | 融合因子=(0,0,0,0) |
D3DBLEND_ONE | 融合因子=(1,1,1,1) |
D3DBLEND_SRCCOLOR | 融合因子=(R_src,G_src,B_src,A_src) |
D3DBLEND_INVSRCCOLOR | 融合因子=(1-R_src,1-G_src,1-B_src,1-A_src) |
D3DBLEND_SRCALPHA | 融合因子=(1-A_src,A_src,A_src,A_src) |
D3DBLEND_INVSRCALPHA | 融合因子=(1-A_src,1-A_src,1-A_src,1-A_src) |
D3DBLEND_DESTALPHA | 融合因子=(A_dst , A_dst, A_dst , A_dst) |
D3DBLEND_INVDESTALPHA | 融合因子= (1-A_dst, 1-A_dst, 1-A_dst , 1-A_dst ). |
D3DBLEND_DESTCOLOR | 融合因子=(R_dst , G_dst, B_dst , A_dst). |
D3DBLEND_INVDESTCOLOR | 融合因子= (1 - R_dst, 1 - G_dst, 1 - B_dst, 1 - A_dst). |
D3DBLEND_SRCALPHASAT | 融合因子= (f, f, f, 1),其中f = min(A_src,1 - A_dst) |
在上表中,R_src , G_src , B_src , A_src分別表示源(即source)畫素的紅、綠、藍、透明四個分量值,而R_dst , G_dst, B_dst , A_dst表示目標(即destination)畫素的紅、綠、藍、透明四個分量值。
大家需要什麼型別的融合因子,在上表中進行查閱就行了。
五、Alpha的三處來源
我們在使用Alpha融合之前,還需要明確源畫素和目標畫素顏色值的Alpha分量來自何方。畫素的Alpha值一般有三處來源,分別是頂點顏色的Alpha值、材質的Alpha值、紋理的Alpha值。我們通常在這三處來源中取一處就可以了。它們的優先順序是這樣的,紋理>材質>頂點顏色。
即這樣理解:
首先我們看有沒有使用紋理貼圖。如果是使用了紋理貼圖,那麼畫素的Alpha值就優先來源於紋理貼圖的Alpha通道。
我是旁白:紋理的Alpha分量是高富帥,首先必須滿足他。
再看有沒有使用光照和材質。如果使用了材質,那麼畫素的Alpha值就優先來源於物體表面的材質。
我是旁白:在紋理的Alpha分量不在場的情況下,備胎“材質的Alpha分量”終於逆襲了。
最後若既沒有使用紋理貼圖也沒有使用光照和材質,那麼畫素的Alpha值就只能來源於頂點的顏色值的Alpha分量了。
我是旁白逐夢便利貼:在紋理的Alpha分量和材質的Alpha分量都不在場的情況下,備胎的備胎“頂點的Alpha分量”也有春天啊。
下面我們分別看看這三處來源如何通過程式碼來指定。
1.頂點Alpha分量
首先我們要知道,頂點Alpha分量只是備胎的備胎而已,它在沒有使用光照和材質的情況下才有上場的機會。
如果在程式中直接指定每個頂點的顏色,那麼可以直接給出每個頂點顏色的Alpha值,並且這些頂點的Alpha值是可以在程式執行過程中動態修改的。
我們可以通過IDirect3DDevice9::SetTextureStageState方法指定Alpha值的來源,把第三個引數指定為D3DTA_DIFFUSE,來指定Alpha值來自頂點顏色。
在MSDN中查到這個函式原型如下:
HRESULT SetTextureStageState( [in] DWORD Stage, [in]D3DTEXTURESTAGESTATETYPE Type, [in] DWORD Value );
■第一個引數,DWORD型別的Stage,指定當前設定的紋理層為第幾層(有效值0~7)
■第二個引數,D3DTEXTURESTAGESTATETYPE型別的Type,填將要設定的紋理渲染狀態,在列舉型別D3DTEXTURESTAGESTATETYPE中任意取值。
■第三個引數,DWORD型別的Value,表示所設定的狀態值,它是根據第二個引數來決定具體取什麼值的。
對於頂點Alpha分量,我們就這樣來兩句:
//計算漫反射顏色的alpha值 g_pd3dDevice->SetTextureStageState(0,D3DTSS_ALPHAARG1, D3DTA_DIFFUSE); g_pd3dDevice->SetTextureStageState(0,D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
2.材質的Alpha分量
之前部分我們講的材質的Alpha分量,充其量也只是個備胎而已,在場景內的物體沒有指定紋理的時候,才有用武之地。在這種情況下,頂點的Alpha值取決於材質屬性中漫反射顏色的Alpha係數以及燈光顏色中的Alpha係數,通過材質和光照中的Alpha係數相互作用,計算得到。我們知道,頂點的光照計算過程是分別針對紅、綠、藍、Alpha這四個顏色分量分開獨立計算的。而我們關注的頂點的Alpha值就決定於光照計算結果的Alpha分量,和其他的紅、綠、藍三個分量毫無瓜葛。
比如我們可以這樣來設定某材質的Alpha分量值,這句程式碼中我們把這種材質的漫反射顏色值的Alpha分量設為了0.2(範圍為0.0~1.0)
g_pMaterial].Diffuse.a= 0.2f;
3.紋理的Alpha分量
作為不可一世的“高富帥”——紋理,既然它在物體表面上使用了,就必須首先滿足它的要求,那麼,畫素的Alpha值就是紋理Alpha混合之後的值了。
所以這時候混合後的畫素就取決於紋理的Alpha混合方式。而紋理Alpha混合方式決定了紋理Alpha混合之後的Alpha值是取自材質,還是取自紋理,抑或是取自這兩者的某種運算。
若是取自紋理,我們就這樣寫:
m_pd3dDevice->SetTextureStageState( 0,D3DTSS_ALPHAARG1, D3DTA_TEXTURE );// Alpha值是取自材質 m_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); //將紋理顏色混合的第一個引數的ALPHA值用於輸出
我是旁白:可以使用DirectX SDK中提供的DirectX Texture Tool來為我們的素材紋理圖片建立Alpha通道
六、Alpha融合使用三步曲
前面講了那麼多,現在依然是落實到一個字“用”上,依舊是為大家總結了一個使用三步曲,方便大家立竿見影,快速掌握Alpha融合技術的使用。
1. 三步曲之一:啟用Alpha融合
在Direct3D中,混合預設是被關閉著的,要啟用Alpha融合的話,我們就通過設定D3DRS_ALPHABLENDENABLE渲染狀態為true,即寫上這句程式碼:
m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
2. 三步曲之二:設定融合因子
啟用了Alpha融合,第二步便是設定源融合因子和目標融合因子。前面我們已經詳細講解過源融合因子和目標融合因子的取值。源融合因子和目標融合因子分別可以在SetRenderState方法中第一個引數取D3DRS_SRCBLEND和D3DRS_DESTBLEND分別進行設定,第二個引數都是在一個D3DBLEND列舉體中進行的取值。
比如這樣寫,就是源融合因子=(A_src , A_src , A_src , A_src),目標融合因子=(1-A_src , 1-A_src , 1-A_src , 1-A_src),這是我們最常用的Alpha混合因子取法:
//設定融合因子 g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA); g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
3. 三步曲之三:設定Alpha融合運算方式
因為Direct3D中有預設的融合運算方式D3DBLENDOP_ADD,即源畫素計算結果與目標畫素的計算結果相加。所以這一步其實可以省略,但是如果不想用這種融合運算方式,我們可以加上這一步,用SetRenderState方法來改成我們需要的運算方式,比如如下的程式碼,便將融合運算方式改成了D3DBLENDOP_SUBTRACT,即源畫素計算結果與目標畫素的計算結果相減:
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_SUBTRACT);
4. 以程式碼為載體理解
依舊是把這三步曲整合一下,在Direct3D中運用Alpha混合技術,其實最少只用寫3句程式碼,第三步通常可以省略,即如下的程式碼:
// 三步曲之一,開啟Alpha融合 g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true); //三步曲之二,設定融合因子 g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); //三步曲之三,設定融合運算方式 g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); //這句設定運算方式為D3DBLENDOP_ADD的程式碼Direct3D預設為我們寫了,所以註釋掉這句也沒大礙
七、詳細註釋的原始碼欣賞
我們今天的這個示例程式其實更準確的是接在學完3D遊戲人物載入那一篇文章的示例程式之後的。這裡我們沒有載入模型的紋理,於是可以看到材質Alpha的效果。在Object_Init( )函式中載入材質時我們將程式碼經過了一些調整,才能達到預先想要的效果。好了,上主要的main函式程式碼吧:
//-----------------------------------【程式說明】---------------------------------------------- // 【Visual C++】遊戲開發系列配套原始碼五十五 淺墨DirectX教程二十二 水乳交融的美學:alpha混合技術 // VS2010版 // 2013年10月 Create by 淺墨 // 背景音樂素材出處:Fort Minor-Where'd You Go //------------------------------------------------------------------------------------------------ //-----------------------------------【巨集定義部分】-------------------------------------------- // 描述:定義一些輔助巨集 //------------------------------------------------------------------------------------------------ #define SCREEN_WIDTH 932 //為視窗寬度定義的巨集,以方便在此處修改視窗寬度 #define SCREEN_HEIGHT 700 //為視窗高度定義的巨集,以方便在此處修改視窗高度 #define WINDOW_TITLE _T("【致我們永不熄滅的遊戲開發夢想】 淺墨DirectX教程二十二 水乳交融的美學:alpha混合技術 博文配套示例程式 by淺墨") //為視窗標題定義的巨集 //-----------------------------------【標頭檔案包含部分】--------------------------------------- // 描述:包含程式所依賴的標頭檔案 //------------------------------------------------------------------------------------------------ #include <d3d9.h> #include <d3dx9.h> #include <tchar.h> #include <time.h> #include "DirectInputClass.h" //-----------------------------------【庫檔案包含部分】--------------------------------------- // 描述:包含程式所依賴的庫檔案 //------------------------------------------------------------------------------------------------ #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") //-----------------------------------【全域性變數宣告部分】------------------------------------- // 描述:全域性變數的宣告 //------------------------------------------------------------------------------------------------ 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類的指標 LPD3DXMESH g_pMesh = NULL; // 網格物件 D3DMATERIAL9* g_pMaterials = NULL; // 網格的材質資訊 LPDIRECT3DTEXTURE9* g_pTextures = NULL; // 網格的紋理資訊 DWORD g_dwNumMtrls = 0; // 材質的數目 //-----------------------------------【全域性函式宣告部分】------------------------------------- // 描述:全域性函式宣告,防止“未宣告的標識”系列錯誤 //------------------------------------------------------------------------------------------------ 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 Matrix_Set(); //-----------------------------------【WinMain( )函式】-------------------------------------- // 描述: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"Fort Minor-Where'd You Go.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; } //-----------------------------------【WndProc( )函式】-------------------------------------- // 描述:視窗過程函式WndProc,對視窗訊息進行處理 //------------------------------------------------------------------------------------------------ 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; //正常退出 } //-----------------------------------【Direct3D_Init( )函式】---------------------------------- // 描述:Direct3D初始化函式,進行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; } //-----------------------------------【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"69.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_pMaterials[i].Diffuse.a = 0.3f;//設定材質的Alpha分量 //建立一下紋理物件 g_pTextures[i] = NULL; //註釋掉紋理的載入,讓“備胎”材質的Alpha通道有上場的機會 //D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]); } SAFE_RELEASE(pAdjBuffer) SAFE_RELEASE(pMtrlBuffer) // 設定光照 D3DLIGHT9 light; ::ZeroMemory(&light, sizeof(light)); light.Type = D3DLIGHT_DIRECTIONAL; light.Ambient = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f); light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); light.Specular = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f); light.Direction = D3DXVECTOR3(1.0f, 0.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_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); //開啟背面消隱 g_pd3dDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESS); //將深度測試函式設為D3DCMP_LESS g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, true); //深度測試成功後,更新深度快取 // 三步曲之一,開啟Alpha融合 g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true); //三步曲之二,設定融合因子 g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); //三步曲之三,設定融合運算方式 g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); //這句設定運算方式為D3DBLENDOP_ADD的程式碼Direct3D預設為我們寫了,所以註釋掉這句也沒大礙 return S_OK; } //-----------------------------------【Matrix_Set( )函式】-------------------------------------- // 描述:封裝了Direct3D四大變換的函式,即世界變換,取景變換,投影變換,視口變換的設定 //-------------------------------------------------------------------------------------------------- void Matrix_Set() { //-------------------------------------------------------------------------------------- //【四大變換之二】:取景變換矩陣的設定 //-------------------------------------------------------------------------------------- D3DXMATRIX matView; //定義一個矩陣 D3DXVECTOR3 vEye(0.0f, 100.0f, -220.0f); //攝像機的位置 D3DXVECTOR3 vAt(0.0f, 40.0f, 0.0f); //觀察點的位置 D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f);//向上的向量 D3DXMatrixLookAtLH(&matView, &vEye, &vAt, &vUp); //計算出取景變換矩陣 g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //應用取景變換矩陣 //-------------------------------------------------------------------------------------- //【四大變換之三】:投影變換矩陣的設定 //-------------------------------------------------------------------------------------- D3DXMATRIX matProj; //定義一個矩陣 D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f,(float)((double)SCREEN_WIDTH/SCREEN_HEIGHT),1.0f, 1000.0f); //計算投影變換矩陣 g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj); //設定投影變換矩陣 //-------------------------------------------------------------------------------------- //【四大變換之四】:視口變換的設定 //-------------------------------------------------------------------------------------- D3DVIEWPORT9 vp; //例項化一個D3DVIEWPORT9結構體,然後做填空題給各個引數賦值就可以了 vp.X = 0; //表示視口相對於視窗的X座標 vp.Y = 0; //視口相對對視窗的Y座標 vp.Width = SCREEN_WIDTH; //視口的寬度 vp.Height = SCREEN_HEIGHT; //視口的高度 vp.MinZ = 0.0f; //視口在深度快取中的最小深度值 vp.MaxZ = 1.0f; //視口在深度快取中的最大深度值 g_pd3dDevice->SetViewport(&vp); //視口的設定 } //-----------------------------------【Direct3D_Update( )函式】-------------------------------- // 描述:不是即時渲染程式碼但是需要即時呼叫的,如按鍵後的座標的更改,都放在這裡 //-------------------------------------------------------------------------------------------------- void Direct3D_Update( HWND hwnd) { //使用DirectInput類讀取資料 g_pDInput->GetInput(); //通過按鍵的按下來控制漫反射Alpha分量值的變化 if (g_pDInput->IsKeyDown(DIK_1)) //按下1鍵 { for (DWORD i=0; i<g_dwNumMtrls; i++) { g_pMaterials[i].Diffuse.a+= 0.001f; } } if (g_pDInput->IsKeyDown(DIK_2)) //按下1鍵 { for (DWORD i=0; i<g_dwNumMtrls; i++) { g_pMaterials[i].Diffuse.a-= 0.001f; } } // 按住滑鼠左鍵並拖動,為平移操作 static FLOAT fPosX = 0.0f, fPosY = -50.0f, fPosZ = 0.0f; if (g_pDInput->IsMouseButtonDown(0)) { fPosX += (g_pDInput->MouseDX())* 0.08f; fPosY += (g_pDInput->MouseDY()) * -0.08f; } //滑鼠滾輪,為觀察點收縮操作 fPosZ += (g_pDInput->MouseDZ())* 0.02f; // 平移物體 if (g_pDInput->IsKeyDown(DIK_A)) fPosX -= 0.005f; if (g_pDInput->IsKeyDown(DIK_D)) fPosX += 0.005f; if (g_pDInput->IsKeyDown(DIK_W)) fPosY += 0.005f; if (g_pDInput->IsKeyDown(DIK_S)) fPosY -= 0.005f; D3DXMatrixTranslation(&g_matWorld, fPosX, fPosY, fPosZ); // 按住滑鼠右鍵並拖動,為旋轉操作 static float fAngleX = 0, fAngleY =0; if (g_pDInput->IsMouseButtonDown(1)) { fAngleX += (g_pDInput->MouseDY())* -0.01f; fAngleY += (g_pDInput->MouseDX()) * -0.01f; } // 旋轉物體 if (g_pDInput->IsKeyDown(DIK_UP)) fAngleX += 0.005f; if (g_pDInput->IsKeyDown(DIK_DOWN)) fAngleX -= 0.005f; if (g_pDInput->IsKeyDown(DIK_LEFT)) fAngleY -= 0.005f; if (g_pDInput->IsKeyDown(DIK_RIGHT)) fAngleY += 0.005f; D3DXMATRIX Rx, Ry; D3DXMatrixRotationX(&Rx, fAngleX); D3DXMatrixRotationY(&Ry, fAngleY); g_matWorld = Rx * Ry * g_matWorld; Matrix_Set(); } //-----------------------------------【Direct3D_Render( )函式】------------------------------- // 描述:使用Direct3D進行渲染 //-------------------------------------------------------------------------------------------------- void Direct3D_Render(HWND hwnd) { //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之一】:清屏操作 //-------------------------------------------------------------------------------------- g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(160, 150, 150), 1.0f, 0); //定義一個矩形,用於獲取主視窗矩形 RECT formatRect; GetClientRect(hwnd, &formatRect); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之二】:開始繪製 //-------------------------------------------------------------------------------------- g_pd3dDevice->BeginScene(); // 開始繪製 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之三】:正式繪製 //-------------------------------------------------------------------------------------- //繪製3D模型 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); //繪製此部分 } //在視窗右上角處,顯示每秒幀數 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.top = 30; static wchar_t strInfo[256] = {0}; swprintf_s(strInfo,-1, L"模型座標: (%.2f, %.2f, %.2f)", g_matWorld._41, g_matWorld._42, g_matWorld._43); g_pTextHelper->DrawText(NULL, strInfo, -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(135,239,136,255)); // 輸出幫助資訊 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" 數字鍵1與2:增大或者縮小材質的Alpha值", -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" 按住滑鼠右鍵並拖動:旋轉模型", -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" W、S、A、D鍵:平移模型 ", -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" ESC鍵 : 退出程式", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之四】:結束繪製 //-------------------------------------------------------------------------------------- g_pd3dDevice->EndScene(); // 結束繪製 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之五】:顯示翻轉 //-------------------------------------------------------------------------------------- g_pd3dDevice->Present(NULL, NULL, NULL, NULL); // 翻轉與顯示 } //-----------------------------------【Get_FPS( )函式】------------------------------------------ // 描述:用於計算每秒幀速率的一個函式 //-------------------------------------------------------------------------------------------------- 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; } //-----------------------------------【Direct3D_CleanUp( )函式】-------------------------------- // 描述:對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_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) }
在程式中,按下數字鍵1,2可以進行alpha值大小的調節。
這次的人物模型來自真三國無雙六,其實真的很精美的,但是為了演示Alpha混合的效果,就不得不出現一些毀三觀的圖。
以下五張,分別是五張不同的alhpa值時的截圖,大家可以重點看一下變化:
最後還是看一張貼上材質和紋理效果後的正常的圖吧,還原我們的三觀:
文章最後,依舊是放出本篇文章配套原始碼的下載:
本節筆記配套原始碼請點選這裡下載:
文章最後,依然是【每文一語】欄目,今天的句子是:
改變,改變。自己為什麼要去改變?
因為不滿現狀,因為有一顆雄心,有一個和現在環境不能吻合的夢想!
下週一,讓我們離遊戲開發的夢想更近一步。
下週一,遊戲開發筆記,我們,不見不散。
淺墨歷時一年為遊戲程式設計愛好者鍛造的著作:《逐夢旅程:Windows遊戲程式設計之從零開始》如果你喜歡淺墨寫的【Visual C++】遊戲開發系列部落格文章,那麼你一定會愛上這本書。這是淺墨專門為熱愛遊戲程式設計的朋友們寫的入門級遊戲程式設計寶典。噹噹網|------------------------------------------------------------------------------------------------------------------------------