上篇文章中我們講解了深度緩存的方方面面,有不少朋友都評論或者發郵件跟淺墨說接下來講一講和深度緩存情同手足的模板緩存相關的技術,於是,這篇文章就誕生了。這篇文章可是費了淺墨不少腦細胞啊,寫了週末整整一天,一萬多字,從早上11點寫到晚上12點-
-。
這篇文章的主角模板技術整體來說比深度測試技術難理解,文中有不懂的地方大家可以多看幾遍,最好是結合文章後面我們提供的配套示例程序的代碼一起理解。好了,我們開始正題吧。
一、對模板技術中概念的理解
想要學習模板技術,有兩個首先的概念需要理解,即模板緩存與模板測試。
1、模板緩存
首先我們瞭解什麼是模板緩存。
模板緩存(stencil buffer)是一個用於專門用於製作特效的離屏(off-screen)緩存。模板緩存的分辨率與之前講過的後臺緩存和深度緩存的分辨率完全相同,模板緩存的像素也後臺緩存、深度緩存中的像素一一對應。正所謂人如其名,模板緩存,模板也,它能讓我們動態地、有針對性地決定是否將某個像素寫到後臺緩存中。
比如,我們稍後會講到的實現鏡面特效,我們只需在鏡子所在的那個特定的平面區域(注意是一片區域,不是整個平面)中繪製出最終幻想裏的遊戲角色“雷霆”的鏡像,而不在鏡子之外做多餘的繪製。這個時候,模板緩存就可以派上用場了。
其實,模板緩存可以理解爲Direct3D中的一個專門來做特效的工具緩存而已。
2、模板測試
在運用模板技術來進行特效的繪製時,需要精確到每個像素。我們會根據每個像素的模板緩存的值,進行一些檢查,最後得出這個像素是否需要繪製的結論,從而實現一些特殊的效果。而這個檢查的過程,就是模板測試。
在Direct3D中,我們常常利用模板測試來實現一些特殊的效果。比如圖形的合成、鏡面特效、消融、淡入淡出、輪廓的顯示、側影和實時陰影等等特效。
二、模板測試精細講解
解釋完基本概念,下面我們就來看看模板測試到底如何使用。
首先說一點,緩衝區和緩存是一個概念,都是根據buffer這個單詞譯過來的,只是根據語境的選擇,有時候我們寫作“緩衝區”,有時候我們寫作“緩存”而已。
1.創建模板緩衝區
首先需要注意,Direct3D在創建深度緩衝區的同時創建了模板緩衝區,而且將深度緩衝區的一部分作爲模板緩衝區使用,就好像上帝(Direct3D)在造人時先創造了亞當(深度緩衝區),再從亞當的身上取一塊肋骨,於是這就有了夏娃(模板緩衝區)。笑:D
既然他們是同時創建的。那麼他們如何創建相關的講解也就是八九不離十。那麼根據我們上篇文章《【Visual
C++】遊戲開發筆記四十五 淺墨DirectX教程十三 深度測試和Z緩存專場》裏講到的,深度緩衝區和模板緩衝區都是在Direct3D初始化時順手創建的,我們在之前講解Direct3D初始化時,在《Direct3D初始化四步曲之三:填內容》中就有提到。
回憶之前的Direct3D初始化四步曲知識,四步曲之三,其實從頭到尾其實就是在填充一個D3DPRESENT_PARAMETERS結構體,下面我們先貼出這個結構體的原型:
-
typedef struct D3DPRESENT_PARAMETERS {
-
UINT BackBufferWidth;
-
UINT BackBufferHeight;
-
D3DFORMAT BackBufferFormat;
-
UINT BackBufferCount;
-
D3DMULTISAMPLE_TYPE MultiSampleType;
-
DWORD MultiSampleQuality;
-
D3DSWAPEFFECT SwapEffect;
-
HWND hDeviceWindow;
-
BOOL Windowed;
-
BOOL EnableAutoDepthStencil;
-
D3DFORMAT AutoDepthStencilFormat;
-
DWORD Flags;
-
UINT FullScreen_RefreshRateInHz;
-
UINT PresentationInterval;
-
} D3DPRESENT_PARAMETERS,*LPD3DPRESENT_PARAMETERS;
在上篇文章中我們說和深度測試相關的參數有兩個,第十個參數EnableAutoDepthStencil和第十一個參數AutoDepthStencilFormat。而今天的模板測試,只有第十一個參數與其相關,那我們就再用模板測試的口吻把這個參數講一遍。
◆第十一個參數,D3DFORMAT類型的AutoDepthStencilFormat,指定AutoDepthStencilFormat的深度緩衝區和模板緩衝區共同的像素格式。具體格式可以在結構體D3DFORMAT中進行選取。我們列舉一些可以選取的值:
D3DFMT_D16 深度緩存用16位存儲每個像素的深度值
D3DFMT_D24X8 深度緩存用24位存儲每個像素的深度值
D3DFMT_D32深度緩存用32位存儲每個像素的深度值
另外提一點,如果針對老掉牙的機器,在創建模板緩衝區之前,需要檢查一下當前的是否支持我們稍後填進去的模板緩衝區格式。也就是在我們的“Direct3D初始化四步曲之二:取信息”中取出信息來看一下我們的設備是否支持模板緩衝區格式,用到的是CheckDeviceFormat函數。因爲現在的顯卡普遍都功能全面,對Direct3D支持很好,很多時候我們並不需要專門去做這一步。
2.清除模板緩衝區
上篇文章結尾部分我們提了一下,Direct3D渲染五步曲的第一步裏面用到的那個Clear方法裏面也有和深度測試相關的內容,下面我們專門來講一下。
Clear方法我們在渲染五步曲一文裏面講過,這裏我們故地重遊一下,也講出點新東西來。
使用模板測試渲染每一幀之前,都需要先清除上一幀保存在模板緩衝區中的模板值。而清除模板緩衝、顏色緩衝區以及深度緩衝區都是這個IDirect3DDevice9::Clear方法的工作。
我們先貼出這個函數的原型:
-
HRESULT Clear(
-
[in] DWORD Count,
-
[in] const D3DRECT *pRects,
-
[in] DWORD Flags,
-
[in] D3DCOLOR Color,
-
[in] float Z,
-
[in] DWORD Stencil
-
);
首先我們附上在《【Visual C++】遊戲開發筆記三十四 淺墨DirectX提高班之三
起承轉合的藝術:Direct3D渲染五步曲》一文中我們對於這個函數原封不動的講解:
◆ 第一個參數,DWORD類型的Count,指定了接下來的一個參數pRect指向的矩形數組中矩形的數量。我們可以這樣說,Count和pRects是一對好基友-o-。如果pRects我們將其設爲NULL的話,這參數必須設爲0。而如果pRects爲有效的矩形數組的指針的話,這個Count必須就爲一個非零值了。
◆ 第二個參數,const D3DRECT類型的*pRects,指向一個D3DRECT結構體的數組指針,表明我們需要清空的目標矩形區域。
◆ 第三個參數,DWORD類型的Flags,指定我們需要清空的緩衝區。它爲D3DCLEAR_STENCIL、D3DCLEAR_TARGET、D3DCLEAR_ZBUFFER的任意組合,分別表示模板緩衝區、顏色緩衝區、深度緩衝區,用“|”連接。
◆ 第四個參數,D3DCOLOR類型的Color,用於指定我們在清空顏色緩衝區之後每個像素對應的顏色值,這裏的顏色用D3DCOLOR表示,後面我們會講到,這裏我們只需要知道一種D3DCOLOR_XRGB(R,G, B)就可以了,這裏的R,G,B爲我們設定的三原色的值,都在0到255之間取值,比如D3DCOLOR_XRGB(123,76, 228)。
◆ 第五個參數,float類型的Z,用於指定清空深度緩衝區後每個像素對應的深度值。
◆ 第六個參數,DWORD類型的Stencil,用於指定清空模板緩衝區之後模板緩衝區中每個像素對應的模板值。
今天的重點是第三個參數,DWORD類型的Flags,指定我們需要清空的緩衝區。它爲D3DCLEAR_STENCIL、D3DCLEAR_TARGET、D3DCLEAR_ZBUFFER的任意組合,分別表示模板緩衝區、顏色緩衝區、深度緩衝區,用“|”連接。
也就是說,我們想在調用Clear方法的時候清空哪個緩衝區,就在這裏寫上,想要清空多個就寫上多個,用“|”連接。
如果我們三種緩衝區都要清理,就這樣寫:
-
g_pd3dDevice->Clear(0,NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(60, 150, 150), 1.0f, 0);
學到如今,這個三個緩衝區基本都介紹到了,所以我們之後的渲染五步曲的第一步就是這三個標識D3DCLEAR_STENCIL、D3DCLEAR_TARGET、D3DCLEAR_ZBUFFER都填了。
3.模板測試相關參數介紹
我們知道,使用模板測試實現各種效果的關鍵是正確設置於模板測試相關的各渲染狀態。
什麼,渲染狀態?好吧,SetRenderState()函數又一次閃亮登場。我們在第一次介紹函數的時候說它的第一個參數在一個龐大的枚舉類型D3DRENDERSTATETYPE中取值,下面我們看看D3DRENDERSTATETYPE中與模板測試相關的函數有哪些:
-
typedef enum D3DRENDERSTATETYPE {
-
……………………
-
D3DRS_STENCILENABLE = 52,
-
D3DRS_STENCILFAIL = 53,
-
D3DRS_STENCILZFAIL = 54,
-
D3DRS_STENCILPASS = 55,
-
D3DRS_STENCILFUNC = 56,
-
D3DRS_STENCILREF = 57,
-
D3DRS_STENCILMASK = 58,
-
D3DRS_STENCILWRITEMASK = 59,
-
……………………
-
-
D3DRS_TWOSIDEDSTENCILMODE = 185,
-
D3DRS_CCW_STENCILFAIL = 186,
-
D3DRS_CCW_STENCILZFAIL = 187,
-
D3DRS_CCW_STENCILPASS = 188,
-
D3DRS_CCW_STENCILFUNC = 189,
-
……………………
-
-
} D3DRENDERSTATETYPE,*LPD3DRENDERSTATETYPE;
這估計是我們《Visual C++遊戲開發筆記》專欄開設以來,發表的四十六篇教程以來,第一次貼出這樣不完整的數據結構來吧。下面我們對這些與模板相關的渲染狀態挨個進行講解:
■ D3DRS_STENCILENABLE:這個渲染狀態用於啓用或者禁用模板處理功能。這個參數指定爲TRUE表示啓用模板處理;指定爲FALSE,則就表示禁用模板處理。
■ D3DRS_STENCILFAIL:這個渲染狀態表示模板測試失敗時進行的模板操作。而進行的模板操作默認爲D3DSTENCILCAPS_KEEP。
■ D3DRS_STENCILZFAIL:該渲染狀態表示模板測試通過時,但是深度測試失敗時進行的模板操作。默認的模板操作依舊是D3DSTENCILCAPS_KEEP。
■ D3DRS_STENCILPASS:這個渲染狀態表示模板測試通過時進行的模板操作。進行的模板操作默認依舊是爲D3DSTENCILCAPS_KEEP。
■ D3DRS_STENCILFUNC:這個渲染狀態可以指定用於模板測試的比較函數。比較函數可以是D3DCMPFUNC枚舉常量之一,該比較函數將通過模板掩碼的模板參考值與模板緩衝區中當前像素的對應模板值比較,如果爲TRUE,則通過模板測試。
■ D3DRS_STENCILREF:這個渲染狀態用於設置模板參考值,默認爲0.
■ D3DRS_STENCILMASK:這個渲染狀態用於設置模板掩碼,決定對模板參考值和模板緩衝區值的哪位進行比較,默認掩碼爲0xffffffff。
■ D3DRS_STENCILWRITEMASK:這個渲染狀態用於指定寫入到模板緩衝區中的數值的掩碼,默認掩碼也爲0xffffffff。
■ D3DRS_TWOSIDEDSTENCILMODE:這個渲染狀態用於激活或者禁用雙面緩衝區。
■ D3DRS_CCW_STENCILFAIL:這個渲染狀態用於設置在啓用了雙面模板緩衝區後,頂點按照逆時針順序組成的多邊形當模板測試失敗時進行的模板操作。
■ D3DRS_CCW_STENCILZFAIL:這個渲染狀態用於設置在啓用了雙面模板緩衝區後,頂點按照逆時針順序組成的多邊形當模板測試成功但深度測試失敗時進行的模板操作。
■ D3DRS_CCW_STENCILPASS:這個渲染狀態用於設置在啓用了雙面模板緩衝區後,頂點按照逆時針順序組成的多邊形當模板測試成功時進行的模板操作。
■ D3DRS_CCW_STENCILFUNC:這個渲染狀態指定了模板測試的比較函數,在我們上篇文章裏講過的D3DCMPFUNC枚舉類型中取值,讓我再一次貼出這枚舉體的定義代碼:
-
typedef enum D3DCMPFUNC {
-
D3DCMP_NEVER = 1,
-
D3DCMP_LESS = 2,
-
D3DCMP_EQUAL = 3,
-
D3DCMP_LESSEQUAL = 4,
-
D3DCMP_GREATER = 5,
-
D3DCMP_NOTEQUAL = 6,
-
D3DCMP_GREATEREQUAL = 7,
-
D3DCMP_ALWAYS = 8,
-
D3DCMP_FORCE_DWORD = 0x7fffffff
-
} D3DCMPFUNC, *LPD3DCMPFUNC;
下面我們通過一個表格,對這些枚舉類型中的成員進行講解說明:
枚舉類型值(比較函數)
|
精析
|
D3DCMP_NEVER
|
深度測試函數總是返回FALSE
|
D3DCMP_LESS
|
測試點深度值小於深度緩衝區中相應值時,返回TRUE,爲默認值
|
D3DCMP_QUAL
|
測試點深度值等於深度緩衝區中相應值時,返回TRUE
|
D3DCMP_LESSEQUAL
|
測試點深度值大於等於深度緩衝區中相應值時,返回TRUE
|
D3DCMP_GREATER
|
測試點深度值大於深度緩衝區中相應值時,返回TRUE
|
D3DCMP_NOTEQUAL
|
測試點深度值不等於深度緩衝區中相應值時,返回TRUE
|
D3DCMP_GREATEREQUAL
|
測試點深度值大於等於深度緩衝區中相應值時,返回TRUE
|
D3DCMP_ALWAYS
|
深度測試函數總是返回TRUE
|
D3DCMP_FORCE_DWORD
|
這個枚舉值一般不用,用於保證將D3DCMPFUNC枚舉類型編譯爲32位
|
對於目標表面上的每一個像素,Direct3D首先將應用程序定義的模板參考值和模板掩碼進行逐位與運算,然後將當前測試的像素在模板緩衝區中的數值與模板掩碼進行逐位與運算,最後根據模板比較函數對得到的結果進行比較,如果模板測試成功,也就是測試結果爲true,那麼該像素就被寫入後臺緩存;如果模板測試失敗的話,也就是測試結果爲false,那麼該像素就不會被寫入後臺緩存,也不會被寫入深度緩存。
另外,上面我們講到的渲染狀態 D3DRS_STENCILFAIL、D3DRS_STENCILZFAIL、D3DRS_STENCILPASS定義了模板測試、深度測試失敗或者通過時進行的模板操作,他們也是在一個枚舉類型中取值,這個枚舉類型是D3DSTENCILOP,這個枚舉類型的定義如下:
-
typedef enum D3DSTENCILOP {
-
D3DSTENCILOP_KEEP = 1,
-
D3DSTENCILOP_ZERO = 2,
-
D3DSTENCILOP_REPLACE = 3,
-
D3DSTENCILOP_INCRSAT = 4,
-
D3DSTENCILOP_DECRSAT = 5,
-
D3DSTENCILOP_INVERT = 6,
-
D3DSTENCILOP_INCR = 7,
-
D3DSTENCILOP_DECR = 8,
-
D3DSTENCILOP_FORCE_DWORD =0x7fffffff
-
} D3DSTENCILOP, *LPD3DSTENCILOP;
我們還是用一個表格來講解:
枚舉類型值(模板操作)
|
精析
|
D3DSTENCILOP_KEEP
|
是默認的選項,表示不更新模板緩衝區中的值
|
D3DSTENCILOP_ZERO
|
將模板緩衝區中的值設爲0
|
D3DSTENCILOP_REPLACE
|
用模板參考值替換模板緩衝區中對應的值
|
D3DSTENCILOP_INCRSAT
|
增加模板緩衝區中的對應數值,如果大於最大值,則等於最大值
|
D3DSTENCILOP_DECRSAT
|
減小模板緩衝區中的對應數值,如果小於最小值,則等於最小值
|
D3DSTENCILOP_INVERT
|
倒置模板測試區中的對應值的數據位
|
D3DSTENCILOP_INCR
|
增加模板緩衝區中對應數值,如果大於最大值,則等於0
|
D3DSTENCILOP_DECR
|
減小模板緩衝區中對應數值,如果小於0,則等於最大值
|
D3DSTENCILOP_FORCE_DWORD
|
這個枚舉值一般不用,用於保證將D3DCMPFUNC枚舉類型編譯爲32位
|
呼,這些參數終於介紹完了,再多介紹幾個的話,恐怕大家就要到歐洲來去醫院探望淺墨了- -。
4.對模板測試的一些理解
模板測試使用模板參考值、模板掩碼、模板比較函數和當前像素在模板緩衝區中的模板值作爲參數,判斷某個像素是否將被寫入到後臺緩衝區中。模板測試的表達式是這樣的:
![]()
其中的ref表示模板參考值,mask表示模板掩碼,value表示模板緩衝中的值,OP表示模板比較函數,而符號“&”則表示模板值或模板參考值與模板掩碼進行按位的與計算。
在Direct3D進行模板測試前,我們需要對模板測試的模板參考值、模板掩碼和模板比較函數進行下設置。需要注意的是,模板參考值的默認值爲0。當然,我們也可以自己親手設置,用的依然是那個號稱萬能的SetRenderState。第一個參數參數渲染狀態我們設爲D3DRS_STENCILREF,而第二個參數就填一個數值(最好是填16進制的),表示需要的模板參考值。
舉個小實例,下面這段代碼我們就把模板參考值設爲了1:
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILREF,0x1);
而模板掩碼用於屏蔽模板參考值和當前測試像素的模板值的某些位,上面我提到過,其默認值爲0xffffffff,表示不屏蔽任何位。而對應的0x000000就表示屏蔽任何位。D3DRS_STENCILMASK與D3DRS_STENCILWRITEMASK這兩個渲染狀態在SetRenderState函數中就是分別表示模板掩碼值和寫掩碼值的。
再舉個小實例,下面這兩句SetRenderState就是在設置模板掩碼值和寫掩碼值,用於屏蔽模板參考值和像素模板值的低十六位:
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xffff0000);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK,0xffff0000);
由於在實用過程中對不同的特效要在SetRenderState中取不同的渲染狀態,所以模板緩存很難總結出一個幾步曲來,這個倒是有點可惜。如果上面這些知識聽得不是很懂,沒關係,下面我們可以在實例代碼中親身體會一下。
說曹操曹操到,接着我們就來看看模板測試的一個非常重要的應用——鏡面特效。
、
三、鏡面特效的實現
鏡面特效是模板測試技術的應用中最簡單的一個。三維遊戲中模擬的自然界,有很多物體表面就可以看做是一塊鏡面,能反射其他物體的鏡像。比如最常見的,水中的倒影、光滑地表上的人物鏡像等等。
淺墨印象較深的是Dota2中飄逸的英雄船長昆卡的技能洪流釋放之後,在地上會留下一潭水,有小兵或者英雄路過的時候,這潭水就會倒影出在這些小兵或者英雄的鏡像來,非常的逼真。對了,Dota2用的引擎是Valve公司爲著名的第一人稱射擊遊戲《半條命2》系列所開發的Source遊戲引擎。Source引擎也被我稱爲次世代引擎、起源引擎,採用C++開發,跨Microsoft Windows、Mac OS X、Xbox、Xbox360、PlayStation
3等衆多平臺。貼一張Source引擎的logo吧:![]()
好了,我們繼續來講。
想要在Direct3D程序中實現鏡面特效,首先需要計算出物體先歸於特定平面中的鏡像,而這個過程可以通過鏡面成像的數學原理來進行計算,然後通過模板技術將物體的鏡像正確地繪製到所指定的平面(鏡面)中。
先來看一下鏡面成像的原理圖:
![]()
上圖中,假設空間中有任意一點q,那麼它相對於平面所成的像就爲q'。
而已知q點的座標,求出q'的座標,就實現了我們鏡面成像的目的。
其實,我們只要通過數學知識,求出q點到q'點的鏡像變換矩陣就可以了,這樣知道q點,根據鏡像變換矩陣,就可以求出q'來。
這個鏡像變換矩陣的求法,微軟早就爲我們準備好了,那就是D3DX庫中的D3DXMatrixReflect函數。我們在MSDN中查到D3DXMatrixReflect的聲明如下:
-
D3DXMATRIX* D3DXMatrixReflect(
-
_Inout_ D3DXMATRIX *pOut,
-
_In_ const D3DXPLANE *pPlane
-
);
■ 第一個參數,D3DXMATRIX類型的*pOut,從類型上來看我們就知道他是一個D3DXMATRIX類型的4 X 4的矩陣,我們調用這個D3DXMatrixReflect方法,其實就是在爲這個矩陣賦值,通過Direct3D的內部計算,讓這個矩陣成爲我們在第二個參數中提供的那個平面的鏡像變換矩陣。
■ 第二個參數,const D3DXPLANE類型的*pPlane,顯然就是一個D3DXPLANE結構體類型的平面了。
D3DXPLANE結構體我們之前沒有遇到過,我們下面來簡單介紹一下。MSDN中對於它是這樣定義的:
-
typedef struct D3DXPLANE {
-
FLOAT a;
-
FLOAT b;
-
FLOAT c;
-
FLOAT d;
-
} D3DXPLANE, *LPD3DXPLANE;
其中的a,b,c,d四個參數顯然就是三維平面方程ax+by+cz=d的四個係數了。
在Direct3D中計算某個物體相對於任意平面的鏡像時,我們只要通過這個D3DXMatrixReflect計算一下該平面的鏡像變換矩陣,然後把該物體的世界變換矩陣乘以鏡像變換矩陣就可以了,得到的結果就是世界變換矩陣。接着我們再SetMatrix一下,接着寫渲染的代碼就可以了。
-
-
D3DXMATRIXmatReflect;
-
D3DXPLANEplane(0.0f, 1.0f, 1.0f, 0.0f);
-
D3DXMatrixReflect(&matReflect,&plane);
-
matWorld=matWorld*matReflect;
-
g_pd3dDevice->SetTransform(D3DTS_WORLD,& matReflect);
-
另外說明一點,在我們當前還在講解的固定渲染流水線中,微軟爲我們把和數學與物理原理相關的內容都封裝起來了,很多時候,我們只要知道這些爲我們封裝好的函數如何使用,什麼情況下使用就好了,而不去深究具體的實現細節。淺墨認爲這是很明智的選擇,無形中大大降低了Direct3D的入門難度。這又說明了我們學習Direct3D,先學固定功能渲染流水線,再學可編程渲染流水線,是最明智,學起來最輕鬆的路線。
爲了降低學習門檻,讓文章更加貼近大衆,通俗易懂,我們也就暫時不深入講解鏡面成像的數學原理了,因爲淺墨知道至少有不少看到數學公式就頭疼的讀者一直在讀淺墨寫的文章。:D
四、通過實例程序講解
好了。鏡面成像原理講完了,我們接下來要着重看一下鏡面特效的使用方法。對應鏡面特效倒是可以整出一個幾步曲來介紹,下面的講解爲了更加清楚,我們結合了本篇文章的配套源代碼一起介紹。因爲我們在講的是渲染特效,所以代碼精髓想都不用想,八九不離十就在Direct3D_Render()函數中。
這個實例程序中我們還是藉助D3DXCreateBox來快捷創建一個薄板作爲鏡子,然後從X文件中載入一個3D人物並繪製出來(我們本次用的是最終幻想中帥氣的女主角雷霆),接着就順理成章地以這個薄板最爲鏡子,在鏡子中繪製出3D人物模型“雷霆”的鏡像。先放一張截圖吧:
![]()
好吧,我們開始講解。
Ⅰ. 清空模板緩存
第一步,在清空模板緩存,並將模板緩存的值都設爲0,用Clear方法完成。這一步我們在Direct3D_Render()函數中渲染五步曲的第一步清屏裏面已經做了,代碼就是這樣:
-
-
-
-
g_pd3dDevice->Clear(0,NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100,150, 0), 1.0f, 0);
Ⅱ.進行常規物體的繪製
這一步也就是包含了渲染五步曲的第二步“開始繪製”,以及第三步“正式繪製”。這一步裏面的代碼基本上就上一節介紹深度緩存時繪製人物模型和牆面的代碼,沒有什麼新鮮的內容:
-
-
-
-
g_pd3dDevice->BeginScene();
-
-
-
-
-
-
D3DXMATRIXmatHero,matWorld,matRotation;
-
-
-
-
D3DXMatrixTranslation(&matHero,-20.0f, 0.0f, -25.0f);
-
matHero=matHero*g_matWorld;
-
g_pd3dDevice->SetTransform(D3DTS_WORLD,&matHero);
-
-
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);
-
}
-
-
-
-
D3DXMatrixTranslation(&matWorld,0.0f,0.0f,0.0f);
-
g_pd3dDevice->SetTransform(D3DTS_WORLD,&matWorld);
-
g_pd3dDevice->SetMaterial(&g_MaterialsWall);
-
g_pMeshWall->DrawSubset(0);
Ⅲ.啓用模板緩存,以及對相關的繪製狀態進行設置
調用一系列的方法來啓用模板緩存,並且對模板比較函數、模板掩碼以及更新模板緩存的渲染狀態進行設置。用了一籮筐的SetRenderState,這一步的代碼如下:
-
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILREF, 0x1);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK,0xffffffff);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
我們在上面的這段代碼中,我們將模板比較函數指定爲模板測試一直成功(D3DCMP_ALWAYS),這就意味着接下來我們繪製的函數總是能通過模板測試。同時,我們指定更新模板緩存的更新方式爲D3DSTENCILOP_REPLACE,也就說,如果模板測試成功時用模板參考值(我們這裏指定的爲0x01)代替模板緩存中的值。
Ⅳ.進行融合操作
這一步裏面,我們關閉向深度緩存中寫的操作,然後啓用融合操作。我們將源融合因子和目標融合因子分別指定爲D3DBLEND_ZERO和D3DBLEND_ONE防止對後臺緩存進行更新。
融合操作我們目前還沒講到過,其實也就是用SetRenderState進行一些渲染狀態的設置而已,後面的文章裏我們會花篇幅講解的,這裏不太理解不要緊,對於鏡面特效我們講解的這幾步而言,其實除了繪製圖形的那兩步對於不同的程序不同以外,其他的幾步代碼都是千篇一律的。
-
-
g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, false);
-
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,true);
-
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
-
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
Ⅴ.確定出鏡面區域
這一步我們主要就是繪製出鏡面區域,也就是指定出待會兒需要作爲鏡子的區域。因爲我之前將模板比較函數設置爲了D3DCMP_ALWAYS,所以鏡面像素無論如何都可以通過模板測試。而且,我們之前還把模板緩存的更新方式設置爲D3DSTENCILOP_REPLACE,那麼在模板緩存中包含鏡面區域的模板值就會被替換爲1,而其他的區域的模板值仍然爲0.這一步的代碼如下:
-
-
D3DXMatrixTranslation(&matWorld,0.0f, 0.0f, 0.0f);
-
g_pd3dDevice->SetTransform(D3DTS_WORLD,&matWorld);
-
g_pd3dDevice->SetMaterial(&g_MaterialsWall);
-
g_pMeshWall->DrawSubset(0);
Ⅵ. 重新設置一系列渲染狀態
確定好鏡面區域後,下面就來重新設置一下之前被改過的渲染狀態和融合狀態,爲後面馬上將要進行的鏡像繪製做準備。把深度緩存的寫操作打開,設置比較函數爲D3DCMP_EQUAL,設置模板緩存的更新方式爲當模板測試通過時保留模板緩衝中原來的值(也就是含有鏡面區域的模板值爲1時,其他區域的模板值爲0),進行一些融合計算,將鏡像與鏡面進行融合。而且我們要關閉背面消隱,也就是將消隱模式設爲D3DCULL_CW,這樣我們在鏡子中看到的纔會是真實的物體背對着我們的那一面在鏡子中的鏡像,不然我們會看到非常奇葩不符合科學和生活常理的鏡像出現。
另外,注意這個時候清空一下Z緩存,因爲接下來我們所繪製的鏡像的深度值必定會大於鏡面的深度值,順着鏡面看的話,按常理鏡像肯定是要被鏡子遮擋住的,這樣我們繪製鏡像之前做的那麼多工作就完全毀於一旦了。所以這個時候必定要調用Clear方法清理一下Z緩存。相關代碼如下:
-
-
g_pd3dDevice->Clear(0,0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);
-
g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
-
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
-
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
-
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
Ⅶ.計算鏡像變換矩陣
這一步就是運用了我們在上面講鏡面特效時的思路,定義出鏡面所在的平面的D3DXPLANE型平面,然後藉助D3DXMatrixReflect來得到鏡像變換矩陣。
-
-
D3DXMATRIXmatReflect;
-
D3DXPLANEplaneXY(0.0f, 0.0f, 1.0f, 0.0f);
-
D3DXMatrixReflect(&matReflect,&planeXY);
-
matWorld= matReflect * matHero;
Ⅷ.繪製鏡像
忙了前面七步,就是爲了現在不會吹灰之力地繪製出鏡像。這一步完全沒有技術含量,先設置一下世界矩陣,然後把第二步裏面繪製物體的代碼原封不動拷過來就行了:
-
-
g_pd3dDevice->SetTransform(D3DTS_WORLD,&matWorld);
-
-
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);
-
}
Ⅸ.恢復渲染狀態
因爲我們的Direct3D_Render()函數在消息循環的驅動下一直在被調用,在繪製完鏡像後,需要把渲染狀態調回來,免得後面其他物體或者下一次調用Direct3D_Render()函數時的渲染受到影響。也就是關閉融合,關閉模板測試,打開背面消隱:
-
-
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
-
g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE, false);
-
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
五、詳細註釋的源代碼欣賞
本篇文章的配套源代碼依舊是包含四個文件,主要用於公共輔助宏定義的D3DUtil.h,用於封裝了DirectInput輸入控制API的DirectInputClass.h和DirectInputClass.cpp最後纔是核心代碼main.cpp。
其實D3DUtil.h,DirectInputClass.h以及DirectInputClass.cpp在上篇文章的配套demo的基礎上並沒有做任何修改,我們只是修改了main.cpp中的代碼而已。鑑於這個三個文件在之前的基礎上無任何修改且在前面的文章中已經貼出過多次,這次就不再費篇幅貼出了,我們只貼出核心代碼main.cpp即可:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#define SCREEN_WIDTH 800 //爲窗口寬度定義的宏,以方便在此處修改窗口寬度
-
#define SCREEN_HEIGHT 600 //爲窗口高度定義的宏,以方便在此處修改窗口高度
-
#define WINDOW_TITLE _T("【Visual C++】遊戲開發筆記系列配套示例程序四十六 淺墨DirectX教程十四 模板緩存與鏡面特效專場") //爲窗口標題定義的宏
-
-
-
-
-
-
-
#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;
-
LPD3DXFONT g_pTextFPS =NULL;
-
LPD3DXFONT g_pTextAdaperName = NULL;
-
LPD3DXFONT g_pTextHelper = NULL;
-
LPD3DXFONT g_pTextInfor = NULL;
-
float g_FPS = 0.0f;
-
wchar_t g_strFPS[50]={0};
-
wchar_t g_strAdapterName[60]={0};
-
D3DXMATRIX g_matWorld;
-
DInputClass* g_pDInput = NULL;
-
-
LPD3DXMESH g_pMesh = NULL;
-
D3DMATERIAL9* g_pMaterials = NULL;
-
LPDIRECT3DTEXTURE9* g_pTextures = NULL;
-
DWORD g_dwNumMtrls = 0;
-
-
LPD3DXMESH g_pMeshWall = NULL;
-
D3DMATERIAL9 g_MaterialsWall;
-
-
-
-
-
-
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();
-
-
-
-
-
-
-
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
-
{
-
-
-
WNDCLASSEX wndClass = { 0 };
-
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);
-
wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
-
wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);
-
wndClass.lpszMenuName = NULL;
-
wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");
-
-
if( !RegisterClassEx( &wndClass ) )
-
return -1;
-
-
HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,
-
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH,
-
SCREEN_HEIGHT, NULL, NULL, hInstance, NULL );
-
-
-
-
if (!(S_OK==Direct3D_Init (hwnd,hInstance)))
-
{
-
MessageBox(hwnd, _T("Direct3D初始化失敗~!"), _T("淺墨的消息窗口"), 0);
-
}
-
PlaySound(L"仙劍·戰鬥3.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP);
-
-
-
-
MoveWindow(hwnd,200,50,SCREEN_WIDTH,SCREEN_HEIGHT,true);
-
ShowWindow( hwnd, nShowCmd );
-
UpdateWindow(hwnd);
-
-
-
g_pDInput = new DInputClass();
-
g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
-
-
-
MSG msg = { 0 };
-
while( msg.message != WM_QUIT )
-
{
-
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;
-
}
-
-
-
-
-
-
-
-
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
-
{
-
switch( message )
-
{
-
case WM_PAINT:
-
Direct3D_Render(hwnd);
-
ValidateRect(hwnd, NULL);
-
break;
-
-
case WM_KEYDOWN:
-
if (wParam == VK_ESCAPE)
-
DestroyWindow(hwnd);
-
break;
-
case WM_DESTROY:
-
Direct3D_CleanUp();
-
PostQuitMessage( 0 );
-
break;
-
-
default:
-
return DefWindowProc( hwnd, message, wParam, lParam );
-
}
-
-
return 0;
-
}
-
-
-
-
-
-
-
-
-
-
-
-
-
HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance)
-
{
-
-
-
-
-
LPDIRECT3D9 pD3D = NULL;
-
if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
-
return E_FAIL;
-
-
-
-
-
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;
-
-
-
-
-
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;
-
-
-
-
-
if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
-
hwnd, vp, &d3dpp, &g_pd3dDevice)))
-
return E_FAIL;
-
-
-
-
wchar_t TempName[60]=L"當前顯卡型號:";
-
D3DADAPTER_IDENTIFIER9 Adapter;
-
pD3D->GetAdapterIdentifier(0,0,&Adapter);
-
int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);
-
MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);
-
wcscat_s(TempName,g_strAdapterName);
-
wcscpy_s(g_strAdapterName,TempName);
-
-
if(!(S_OK==Objects_Init())) return E_FAIL;
-
-
SAFE_RELEASE(pD3D)
-
-
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);
-
-
-
-
-
-
LPD3DXBUFFER pAdjBuffer = NULL;
-
LPD3DXBUFFER pMtrlBuffer = NULL;
-
-
D3DXLoadMeshFromX(L"lighting.X", D3DXMESH_MANAGED, g_pd3dDevice,
-
&pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh);
-
-
-
D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer();
-
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)
-
-
-
-
-
-
-
D3DXCreateBox(g_pd3dDevice, 120.0f, 120.0f, 0.3f, &g_pMeshWall, NULL);
-
g_MaterialsWall.Ambient = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
-
g_MaterialsWall.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
-
g_MaterialsWall.Specular = D3DXCOLOR(0.2f, 1.0f, 1.0f, 1.0f);
-
-
-
-
-
D3DLIGHT9 light;
-
::ZeroMemory(&light, sizeof(light));
-
light.Type = D3DLIGHT_DIRECTIONAL;
-
light.Ambient = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
-
light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
-
light.Specular = D3DXCOLOR(1.0f, 1.0f, 1.0f, 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);
-
-
-
-
return S_OK;
-
}
-
-
-
-
-
-
-
-
-
-
-
-
void Matrix_Set()
-
{
-
-
-
-
-
D3DXMATRIX matView;
-
D3DXVECTOR3 vEye(100.0f, 0.0f, -250.0f);
-
D3DXVECTOR3 vAt(0.0f, 0.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;
-
vp.X = 0;
-
vp.Y = 0;
-
vp.Width = SCREEN_WIDTH;
-
vp.Height = SCREEN_HEIGHT;
-
vp.MinZ = 0.0f;
-
vp.MaxZ = 1.0f;
-
g_pd3dDevice->SetViewport(&vp);
-
-
}
-
-
-
void Direct3D_Update( HWND hwnd)
-
{
-
-
g_pDInput->GetInput();
-
-
-
if (g_pDInput->IsKeyDown(DIK_1))
-
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, true);
-
if (g_pDInput->IsKeyDown(DIK_2))
-
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false);
-
-
-
-
static FLOAT fPosX = 0.0f, fPosY = -1.5f, 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.05f;
-
if (g_pDInput->IsKeyDown(DIK_DOWN)) fAngleX -= 0.05f;
-
if (g_pDInput->IsKeyDown(DIK_LEFT)) fAngleY -= 0.05f;
-
if (g_pDInput->IsKeyDown(DIK_RIGHT)) fAngleY += 0.05f;
-
-
-
D3DXMATRIX Rx, Ry;
-
D3DXMatrixRotationX(&Rx, fAngleX);
-
D3DXMatrixRotationY(&Ry, fAngleY);
-
-
g_matWorld = Rx * Ry * g_matWorld;
-
-
Matrix_Set();
-
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
void Direct3D_Render(HWND hwnd)
-
{
-
-
-
-
-
g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100, 150, 0), 1.0f, 0);
-
-
-
RECT formatRect;
-
GetClientRect(hwnd, &formatRect);
-
-
-
-
-
g_pd3dDevice->BeginScene();
-
-
-
-
-
-
D3DXMATRIX matHero,matWorld,matRotation;
-
-
-
-
D3DXMatrixTranslation(&matHero, -20.0f, 0.0f, -25.0f);
-
matHero=matHero*g_matWorld;
-
g_pd3dDevice->SetTransform(D3DTS_WORLD, &matHero);
-
-
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);
-
}
-
-
-
-
D3DXMatrixTranslation(&matWorld, 0.0f,0.0f,0.0f);
-
g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);
-
g_pd3dDevice->SetMaterial(&g_MaterialsWall);
-
g_pMeshWall->DrawSubset(0);
-
-
-
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILREF, 0x1);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
-
-
-
g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, false);
-
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
-
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
-
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
-
-
-
D3DXMatrixTranslation(&matWorld, 0.0f, 0.0f, 0.0f);
-
g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);
-
g_pd3dDevice->SetMaterial(&g_MaterialsWall);
-
g_pMeshWall->DrawSubset(0);
-
-
-
g_pd3dDevice->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);
-
g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
-
g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
-
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
-
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
-
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
-
-
-
D3DXMATRIX matReflect;
-
D3DXPLANE planeXY(0.0f, 0.0f, 1.0f, 0.0f);
-
D3DXMatrixReflect(&matReflect, &planeXY);
-
matWorld = matReflect * matHero;
-
-
-
-
g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);
-
-
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->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
-
g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE, false);
-
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
-
-
-
-
-
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)", matHero._41, matHero._42, matHero._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:開啓或者關閉深度測試", -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));
-
-
-
-
-
-
g_pd3dDevice->EndScene();
-
-
-
-
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
-
-
}
-
-
-
-
-
-
-
-
-
float Get_FPS()
-
{
-
-
-
static float fps = 0;
-
static int frameCount = 0;
-
static float currentTime =0.0f;
-
static float lastTime = 0.0f;
-
-
frameCount++;
-
currentTime = timeGetTime()*0.001f;
-
-
-
if(currentTime - lastTime > 1.0f)
-
{
-
fps = (float)frameCount /(currentTime - lastTime);
-
lastTime = currentTime;
-
frameCount = 0;
-
}
-
-
return fps;
-
}
-
-
-
-
-
-
-
-
void Direct3D_CleanUp()
-
{
-
-
-
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_pMeshWall);
-
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在開啓深度測試和關閉深度測試之間切換,不過建議不要去關閉深度測試,不然會被毀三觀的。。。關了深度測試後的冷美人“雷霆”醜得慘不忍睹。。。。
因爲上面已經把最主要最精髓的Direct3D_Render()函數大卸九塊了,這個示例程序大家理解起來應該就不是什麼問題了。需要提醒大家一點,我們把觀察點的位置改了一下,更利於觀察鏡面的效果了。
-
D3DXVECTOR3 vEye(100.0f, 0.0f, -250.0f);
另外,這個事例程序中因爲是在對所有物體的鏡面特效的實現做一個籠統的代表,所以就沒有針對這個3D模型做相應的渲染效果的優化,所以有可能還有一點點看起來不科學的地方,不過無傷大雅的。
下面放出示例程序的截圖:
![]()
![]()
![]()
![]()
這個示例程序中的背景音樂用的是《仙劍奇俠傳3問情篇》中的一首戰鬥音樂,有些小鬧騰的。
文章最後,依舊是放出本篇文章配套源代碼的下載:
本節筆記配套源代碼請點擊這裏下載:
【淺墨DirectX提高班】配套源代碼之十四下載
以上就是本節筆記的全部內容,更多精彩內容,且聽下回分解。
淺墨在這裏,希望喜歡遊戲開發系列文章的朋友們能留下你們的評論,每次淺墨登陸博客看到大家的留言的時候都會非常開心,感覺自己正在傳遞一種信仰,一種精神。
文章最後,依然是【每文一語】欄目,今天的句子是:
想一千次,不如去做一次。華麗的跌倒,勝過無謂的徘徊。
![]()