首先咱們瞭解什麼是模板緩存。算法
模板緩存(stencil buffer)是一個用於專門用於製做特效的離屏(off-screen)緩存。模板緩存的分辨率與以前講過的後臺緩存和深度緩存的分辨率徹底相同,模板緩存的像素也後臺緩存、深度緩存中的像素一一對應。正所謂人如其名,模板緩存,模板也,它能讓咱們動態地、有針對性地決定是否將某個像素寫到後臺緩存中。編程
好比,咱們稍後會講到的實現鏡面特效,咱們只需在鏡子所在的那個特定的平面區域(注意是一片區域,不是整個平面)中繪製出最終幻想裏的遊戲角色「雷霆」的鏡像,而不在鏡子以外作多餘的繪製。這個時候,模板緩存就能夠派上用場了。數組
其實,模板緩存能夠理解爲Direct3D中的一個專門來作特效的工具緩存而已。緩存
在運用模板技術來進行特效的繪製時,須要精確到每一個像素。咱們會根據每一個像素的模板緩存的值,進行一些檢查,最後得出這個像素是否須要繪製的結論,從而實現一些特殊的效果。而這個檢查的過程,就是模板測試。數據結構
在Direct3D中,咱們經常利用模板測試來實現一些特殊的效果。好比圖形的合成、鏡面特效、消融、淡入淡出、輪廓的顯示、側影和實時陰影等等特效。函數
首先須要注意,Direct3D在建立深度緩衝區的同時建立了模板緩衝區,並且將深度緩衝區的一部分做爲模板緩衝區使用,就好像上帝(Direct3D)在造人時先創造了亞當(深度緩衝區),再從亞當的身上取一塊肋骨,因而這就有了夏娃(模板緩衝區)。笑:D工具
既然他們是同時建立的。那麼他們如何建立相關的講解也就是八九不離十。那麼根據咱們上篇文章《【Visual C++】遊戲開發筆記四十五 淺墨DirectX教程十三 深度測試和Z緩存專場》裏講到的,深度緩衝區和模板緩衝區都是在Direct3D初始化時順手建立的,咱們在以前講解Direct3D初始化時,在《Direct3D初始化四步曲之三:填內容》中就有提到。學習
回憶以前的Direct3D初始化四步曲知識,四步曲之三,其實從頭至尾其實就是在填充一個D3DPRESENT_PARAMETERS結構體,下面咱們先貼出這個結構體的原型:測試
在上篇文章中咱們說和深度測試相關的參數有兩個,第十個參數EnableAutoDepthStencil和第十一個參數AutoDepthStencilFormat。而今天的模板測試,只有第十一個參數與其相關,那咱們就再用模板測試的口吻把這個參數講一遍。spa
◆第十一個參數,D3DFORMAT類型的AutoDepthStencilFormat,指定AutoDepthStencilFormat的深度緩衝區和模板緩衝區共同的像素格式。具體格式能夠在結構體D3DFORMAT中進行選取。咱們列舉一些能夠選取的值:
D3DFMT_D16 深度緩存用16位存儲每一個像素的深度值
D3DFMT_D24X8 深度緩存用24位存儲每一個像素的深度值
D3DFMT_D32深度緩存用32位存儲每一個像素的深度值
另外提一點,若是針對老掉牙的機器,在建立模板緩衝區以前,須要檢查一下當前的是否支持咱們稍後填進去的模板緩衝區格式。也就是在咱們的「Direct3D初始化四步曲之二:取信息」中取出信息來看一下咱們的設備是否支持模板緩衝區格式,用到的是CheckDeviceFormat函數。由於如今的顯卡廣泛都功能全面,對Direct3D支持很好,不少時候咱們並不須要專門去作這一步。
上篇文章結尾部分咱們提了一下,Direct3D渲染五步曲的第一步裏面用到的那個Clear方法裏面也有和深度測試相關的內容,下面咱們專門來說一下。
Clear方法咱們在渲染五步曲一文裏面講過,這裏咱們故地重遊一下,也講出點新東西來。
使用模板測試渲染每一幀以前,都須要先清除上一幀保存在模板緩衝區中的模板值。而清除模板緩衝、顏色緩衝區以及深度緩衝區都是這個IDirect3DDevice9::Clear方法的工做。
咱們先貼出這個函數的原型:
首先咱們附上在《【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方法的時候清空哪一個緩衝區,就在這裏寫上,想要清空多個就寫上多個,用「|」鏈接。
若是咱們三種緩衝區都要清理,就這樣寫:
學到現在,這個三個緩衝區基本都介紹到了,因此咱們以後的渲染五步曲的第一步就是這三個標識D3DCLEAR_STENCIL、D3DCLEAR_TARGET、D3DCLEAR_ZBUFFER都填了。
咱們知道,使用模板測試實現各類效果的關鍵是正確設置於模板測試相關的各渲染狀態。
什麼,渲染狀態?好吧,SetRenderState()函數又一次閃亮登場。咱們在第一次介紹函數的時候說它的第一個參數在一個龐大的枚舉類型D3DRENDERSTATETYPE中取值,下面咱們看看D3DRENDERSTATETYPE中與模板測試相關的函數有哪些:
這估計是咱們《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枚舉類型中取值,讓我再一次貼出這枚舉體的定義代碼:
下面咱們經過一個表格,對這些枚舉類型中的成員進行講解說明:
枚舉類型值(比較函數) |
精析 |
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,這個枚舉類型的定義以下:
咱們仍是用一個表格來說解:
枚舉類型值(模板操做) |
精析 |
D3DSTENCILOP_KEEP |
是默認的選項,表示不更新模板緩衝區中的值 |
D3DSTENCILOP_ZERO |
將模板緩衝區中的值設爲0 |
D3DSTENCILOP_REPLACE |
用模板參考值替換模板緩衝區中對應的值 |
D3DSTENCILOP_INCRSAT |
增長模板緩衝區中的對應數值,若是大於最大值,則等於最大值 |
D3DSTENCILOP_DECRSAT |
減少模板緩衝區中的對應數值,若是小於最小值,則等於最小值 |
D3DSTENCILOP_INVERT |
倒置模板測試區中的對應值的數據位 |
D3DSTENCILOP_INCR |
增長模板緩衝區中對應數值,若是大於最大值,則等於0 |
D3DSTENCILOP_DECR |
減少模板緩衝區中對應數值,若是小於0,則等於最大值 |
D3DSTENCILOP_FORCE_DWORD |
這個枚舉值通常不用,用於保證將D3DCMPFUNC枚舉類型編譯爲32位 |
這些參數終於介紹完了
模板測試使用模板參考值、模板掩碼、模板比較函數和當前像素在模板緩衝區中的模板值做爲參數,判斷某個像素是否將被寫入到後臺緩衝區中。模板測試的表達式是這樣的:
其中的ref表示模板參考值,mask表示模板掩碼,value表示模板緩衝中的值,OP表示模板比較函數,而符號「&」則表示模板值或模板參考值與模板掩碼進行按位的與計算。
在Direct3D進行模板測試前,咱們須要對模板測試的模板參考值、模板掩碼和模板比較函數進行下設置。須要注意的是,模板參考值的默認值爲0。固然,咱們也能夠本身親手設置,用的依然是那個號稱萬能的SetRenderState。第一個參數參數渲染狀態咱們設爲D3DRS_STENCILREF,而第二個參數就填一個數值(最好是填16進制的),表示須要的模板參考值。
舉個小實例,下面這段代碼咱們就把模板參考值設爲了1:
而模板掩碼用於屏蔽模板參考值和當前測試像素的模板值的某些位,上面我提到過,其默認值爲0xffffffff,表示不屏蔽任何位。而對應的0x000000就表示屏蔽任何位。D3DRS_STENCILMASK與D3DRS_STENCILWRITEMASK這兩個渲染狀態在SetRenderState函數中就是分別表示模板掩碼值和寫掩碼值的。
再舉個小實例,下面這兩句SetRenderState就是在設置模板掩碼值和寫掩碼值,用於屏蔽模板參考值和像素模板值的低十六位:
因爲在實用過程當中對不一樣的特效要在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類型的*pOut,從類型上來看咱們就知道他是一個D3DXMATRIX類型的4 X 4的矩陣,咱們調用這個D3DXMatrixReflect方法,其實就是在爲這個矩陣賦值,經過Direct3D的內部計算,讓這個矩陣成爲咱們在第二個參數中提供的那個平面的鏡像變換矩陣。
■ 第二個參數,const D3DXPLANE類型的*pPlane,顯然就是一個D3DXPLANE結構體類型的平面了。
D3DXPLANE結構體咱們以前沒有遇到過,咱們下面來簡單介紹一下。MSDN中對於它是這樣定義的:
其中的a,b,c,d四個參數顯然就是三維平面方程ax+by+cz=d的四個係數了。
在Direct3D中計算某個物體相對於任意平面的鏡像時,咱們只要經過這個D3DXMatrixReflect計算一下該平面的鏡像變換矩陣,而後把該物體的世界變換矩陣乘以鏡像變換矩陣就能夠了,獲得的結果就是世界變換矩陣。接着咱們再SetMatrix一下,接着寫渲染的代碼就能夠了。
另外說明一點,在咱們當前還在講解的固定渲染流水線中,微軟爲咱們把和數學與物理原理相關的內容都封裝起來了,不少時候,咱們只要知道這些爲咱們封裝好的函數如何使用,什麼狀況下使用就行了,而不去深究具體的實現細節。淺墨認爲這是很明智的選擇,無形中大大下降了Direct3D的入門難度。這又說明了咱們學習Direct3D,先學固定功能渲染流水線,再學可編程渲染流水線,是最明智,學起來最輕鬆的路線。