遊戲開發基礎(八)

 

第八章
模板緩存是一個用於獲得某種特效的離屏緩存
模板緩存的分辨率與後臺緩存和深度緩存的分辨率完全相同,所有模板緩存中的像素與後臺緩存和深度緩存中的像素是一一對應的
模板緩存的功能與模板類似,允許我們動態地,有針對性地決定是否將某個像素寫入後臺緩存中
在Direct3D初始化時,首先必須查詢當前設備是否支持模板緩存,如果支持,還必須啓用,爲了啓用模板緩存,必須將繪製狀態D3DRS_STENCILENABLE設爲true,如要禁用模板緩存,則該狀態應設爲false
Device->SetRenderState(D3DRS_STENCILENABLE,true);
Device->SetRenderState(D3DRS_STENCILENABLE,false);
DirextX9.0中增加了雙面模板特性,該功能可通過削減繪製陰影體所需要的繪製路徑,從而提升陰影體的繪製速度
IDirect3DDevice9::Clear方法將模板緩存清空爲一個默認值,該方法也可對後臺緩存的深度緩存進行清空操作
Device->Clear(0,0,D3DCLEAR_TAEGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL,0xff000000,1.0f,0);
注意第三個參數增加了D3DCLEAR_STENCIL表明我們要對模板緩存,目標緩存(後臺緩存)和深度緩存進行清空操作,第6個參數用於指定要將模板緩存清爲何值,這裏,我們設爲0;

依據nVIDIA報告,在現在硬件中使用模板緩存可被認爲是"沒有計算開銷的"運算,前提是你已經在使用深度緩存

模板緩存可與深度緩存一同創建,爲深度緩存指定格式時,我們可以同時指定模板緩存格式,實際上,模板緩存和深度緩存共享同一個離屏的表面緩存,而每個像素的內存段被劃分爲若干部分,分別與某種特定緩存相對應,考慮3種深度/模板緩存格式:
#D3DFMT_D24S8  含義是,已創建了一個32位深度/模板緩存,其中每個像素的24位指定給深度緩存,8位指定給模板緩存
#D3DFMT_D24X4S4 含義是,已創建了一個32位深度/模板緩存,其中每個像素的24位指定給深度緩存,4位指定給模板緩存,其餘4位不用
#D3DFMT_D15S1 含義是,已創建了一個16位深度/模板緩存,其中每個像素的15位指定給深度緩存,1位指定給模板緩存
注意,一些格式沒有爲模板緩存分配任何空間,例如,D3DFMT_D32格式僅創建一個32位深度緩存
可用模板緩存來阻止對後臺緩存中某些特定區域進行繪製,判斷是否將某個像素寫入後臺緩存的決策過程稱爲模板測試
假定模板已處於啓用狀態,則每個像素都需要進行模板測試,模板測試需要如下兩個操作數:
#左操作數(LHS = ref & mask)該值由應用程序定義的模板參考值(ref)和模板掩碼(mask)通過按位與運算得到
#右操作數(RHS = value & mask)該值由當前進行測試的像素的模板緩存中的數值(value)與模板掩碼(mask)通過按位與運算得到
模板測試的下一步是依據comparison operation 所指定的比較規則對LHS和RHS進行比較,上述表達式的運算結果爲布爾類型(true或false),如果測試爲true,將該像素寫入後臺緩存,如果測試爲false,將阻止寫入後臺緩存,當然當一個像素不被寫入後臺緩存時,也不會被寫入深度緩存。
我們可以對模板參考值,模板掩碼,以及對比較函數進行設定

模板參考值(stencil reference value)ref 的默認值爲0 ,但我們可用D3DRS_STENCILREF繪製狀態改變該值。

例:將模板參考值設爲1
 Device->SetRenderState(D3DRS_STENCILREF,0x01);

模板掩碼(mask)用於屏蔽(隱藏)ref 和value變量中的某些位,其默認值爲0xffffffff,表示不屏蔽任何位,我們可藉助繪製狀態D3DRS_STENCILMASK來修改該掩碼值,
例:屏蔽高6位;
 Devie->SetRenderState(D3DRS_STENCILMASK,0x0000ffff);
模板值是當前待測試像素在模板緩存中的對應值,
例:對第i行,第j列的像素進行測試,則value就是模板緩存中第i行,第j列的值,我們不能顯式第單獨設置模板值,但可以對模板緩存進行清空操作,可以用模板的繪製狀態控制將要寫入模板緩存的內容

可以通過繪製狀態D3DRS_STENCILFUNC來設置比較運算函數,該比較運算函數可取自枚舉類型D3DCMPFUNC
typedef enum D3DCMPFUNC
{
D3DCMP_NEVER = 1,//比較函數總是返回false
D3DCMP_LESS = 2 ,//若LHS < RHS則模板測試成功
D3DCMP_EQUAL = 3,//若LHS = RHS則模板測試成功
D3DCMP_LESSEQUAL = 4,//若LHS <= RHS則模板測試成功
D3DCMP_GREATER = 5,//若LHS > RHS則模板測試成功
D3DCMP_NOTEQUAL = 6,//若(LHS = RHS) == false 則模板測試成功
D3DCMP_GREATEREQUAL = 7,//若LHS >= RHS則模板測試成功
D3DCMP_ALWAYS = 8,//比較函數總是返回true
D3DCMP_FORCE_DWORD = 0x7fffffff,
}D3DCMPFUNC,*LPD3DCMPFUNC;

定義模板緩存中的值如何進行更新:
#第i行,第j列的像素模板測試失敗,這種情況下,我們可藉助繪製狀態D3DRS_STENCILFAIL將模板緩存中處於同樣位置的項的更新方式定義如下:Device->SetRenderState(D3DRS_STENCILFAIL,StencilOperation);
#第i行,第j列的像素深度測試失敗,可藉助繪製狀態D3DRS_STENCILZFAIL將模板緩存中處於同樣位置的項的更新方式:
Device->SetRenderState(D3DRS_STENCILFAIL,StencilOperation);
#第i行,第j列的像素深度測試和模板測試均成功,可藉助繪製狀態D3DRS_STENCILPASS將模板緩存中處於同樣位置的項的更新方式定義如下:
Device->SetRenderState(D3DRS_STENCILPASS,StencilOperation);
StencilOperation可取一下預定義常量:
D3DSTENCILOP_KEEP 不更新模板緩存中的值(保留當前值)
D3DSTENCILOP_ZERO 將模板緩存中的值設爲0
D3DSTENCILOP_REPLACE 用模板參考值替代模板緩存中的對應值
D3DSTENCILOP_INCRSAT 增加模板緩存中的對應數值,如果超過最大值,取最大值
D3DSTENCILOP_DECRSAT 減小模板緩存中的對應數值,如果小於最小值,取最小值
D3DSTENCILOP_INVERT  增加模板緩存中的對應數值,如果超過最大值,則取0
D3DSTENCILOP_DECR    減小模板緩存中的對應數值,如果如果小於0 ,則取最大值

設置寫掩碼(write mask),該值可屏蔽我們將寫入模板緩存的任何值的某些位,可用繪製狀態D3DRS_STENCILWRITEMASK來設定寫掩碼的值,其默認值爲0xffffffff。
例:對高16位屏蔽:
 Device->SetRenderState(D3DRS_STENCILWRITEMASK,0x0000ffff);

實現鏡面效果,需解決兩個問題:首先,瞭解任意平面物體如何成像,其次,將某一表面區域"標記"爲鏡面,然後,就可用只繪製處於鏡面區域中那部分物體的映像了,
如何成像,藉助向量幾何很容易解決,"標記"鏡面藉助模板緩存來解決
D3DX庫提供了下列函數用於創建相對於任意平面的鏡像變換矩陣
D3DXMATRIX* D3DXMatrixReflect(
D3DXMATRIX* pOut,
CONST D3DXPLANE * pPlane,
)

鏡面效果實現步驟:
(1)像往常那樣繪製整個場景(地板,牆壁和茶壺)但先不繪製茶壺的映像,注意,本步中尚不需修改模板緩存
(2)將模板緩存清0
(3)將構成鏡面的圖元僅繪製到模板緩存中,將模板測試設置爲總成功,並指定如果測試通過,模板緩存值被替換爲1,因爲我們僅繪製鏡面,
(4)現在我們將茶壺的映像繪製到後臺緩存和模板緩存中,但要注意,如果通過了模板測試,我們將茶壺的映像僅繪製到後臺緩存中,這次我們設置爲如果模板緩存值爲1,模板測試就一定成功,按照這種方式,茶壺僅被繪製到那個在對應的模板緩存中值爲1的區域中,由於在模板緩存中,只有對應於鏡面區域的部分的模板值爲1,所以茶壺的映像將只繪製到鏡面所在的子區域中。

例:
void RenderMirror()
{
 Device->SetRenderState(D3DRS_STENCILENABLE,true);
 Device->SetRenderState(D3DRS_STENCILFUNC,D3DCMP_ALWAYS);//模板比較運算函數設爲D3DCMP_ALWAYS,測試總會成功
 Device->SetRenderState(D3DRS_STENCILREF,0x1);
 Device->SetRenderState(D3DRS_STENCILMASK,0xffffffff);
 Device->SetRenderState(D3DRS_STENCILWRITEMASK,0xffffffff);
 //如果深度測試失敗,我們模板操作指定爲D3DSTENCILOP_KEEP,意味不對模板緩存中的值進行更新,即保留其當前值,我這樣做,是因爲深度測試失敗就意味着當前測試中的像素被遮擋,而我們也不希望將物體映像到某些部分繪製到被遮擋的像素上
 Device->SetRenderState(D3DRS_STENCILZFAIL,D3DSTENCILOP_KEEP);//
 //如果模板測試失敗,我們仍將模板操作指定爲 D3DSTENCILOP_KEEP
 Device->SetRenderState(D3DRS_STENCILFAIL,D3DSTENCILOP_KEEP);
 //如果深度測試和模板測試均通過,我們將模板緩存的操作方式設定爲D3DSTENCILOP_REPLACE,意味用模板參考值(0x1)替代模板緩存中的對應值
 Device->SetRenderState(D3DRS_STENCILPASS,D3DSTENCILOP_REPLACE);
}

可將繪製狀態D3DRS_ZWRITEENABLE設爲false阻止對深度緩存中進行寫操作
可藉助融合操作並將源融合因子和目標融合因子分別設爲D3DBLEND_ZERO和D3DBLEND_ONE來阻止對後臺緩存的更新,將這些融合因子代入融合方程,可推知後臺緩存沒有發生任何變化
FinalPixel = sourePixel * (0,0,0,0) + DestPixel * (1,1,1,1) = (0,0,0,0) + DestPixel = DestPixel

Device->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_ZERO);
Device->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);

對繪製狀態進行設置
Device->SetRenderState(D3DRS_STENCILFUNC,D3DCMP_EQUAL);
Device->SetRenderState(D3DRS_STENCILPASS,D3DSTENCILOP_KEEP);
現在我們就設置了一個新的比較操作,然後我們進行模板測試:
(ref & mask) == (value & mask)
(0x1 & 0xffffffff) == (value & 0xffffffff)
(0x1) == (value & 0xffffffff)
這表明僅當value=0x1時模板測試纔會成功,由於value僅在模板緩存中對應於該鏡面的區域中才爲0x01,所有僅當我們要在那些區域中進行繪製時,模板測試纔會成功,這樣上面的茶壺映像只會被繪製到鏡面中,而不會被繪製到其他表面上
注意,將繪製狀態 D3DRS_STENCILPASS設爲了D3DSTENCILOP_KEEP,
意味如果測試成功,則保留模板緩存中的值,所以,該模板緩存(模板操作方式爲D3DSTENCILOP_KEEP)中的值沒有發生變化。只用模板緩存來標記那些對應於鏡面區域的像素

爲了不遮擋構成鏡面的圖元,我們將深度緩存清空:
Device->Clear(0,0,D3DCLEAR_ZBUFFEER,0,1.0f,0);
然後我們還要進行融合操作,融合公式:
 FinalPixal = sourcePixel*destPixel + destPixel*(0,0,0,0) = sourcePixel*destPixel
Device->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_DESTCOLOR);
Device->SetRenderState(D3DRS_DESTLEND,D3DBLEND_ZERO);

當物體在鏡面中成像時,其正面和背面將會相互調換,但繞序並未發生變化,所以,"新"正面的繞序將使Direct3D誤認爲它們是背面,類似的"新"背面的繞序會使Direct3D誤認爲它們是正面,所以,徐改變背面消隱模式

將融合操作和模板操作禁用,並恢復先前的消隱模式
Device->SetRenderState(D3DRS_ALPHABLENDENABLE,false)
Device->SetRenderState(D3DRS_STENCILENABLE,false)
Device->SetRenderState(D3DRS_CULLIMODE,D3DCULL_CCW);

當給定投影平面和描述平行光光源(w = 0時)或點光源(w = 1 )的向量時,可以D3DX庫的函數創建陰影矩陣:
D3DXMATRIX * D3DXMatrixShadow(
         D3DXMATRIX* pOut,
         CONST D3DXVECTOR4* pLight,
         CONST D3DXPLANE* pPlane);

藉助模板緩存,將模板測試設置爲只接受第一次得到繪製的那些像素,即在向後臺緩存繪製陰影的像素時,對相應的模板緩存值進行標記,然後,如果企圖將一個像素寫入已經被繪製(模板緩存中已被標記的位置)的區域時,模板測試就會失敗,這樣就防治了重疊像素寫入,並由此避免了二次融合的發生。

將模板比較函數設爲D3DCMP_EQUAL,將D3DRS_STENCILREF繪製狀態設爲0x0,這就指定了如果模板緩存中對應值爲0,陰影就被繪製到後臺緩存中,由於模板緩存已被清零(0x0),當我們首次將陰影的像素寫入時,模板測試結果爲真,但由於已將繪製狀態D3DRS_STENCILPASS設爲D3DSTENCILOP_INCR,所以如果試圖對一個已被寫入的像素進行寫操作,模板測試就會失敗,當某像素被首次寫入時,該像素的模板值就會增加爲0x1,所以當試圖再次對該像素進行寫操作時,模板測試就會失敗,這樣就避免了對一個像素多次寫入,從而避免了二次融合的發生

模板緩存和深度緩存共享同一個表面存儲區,所以二者是同時創建的,我們可用D3DFORMAT類型指定深度/模板表面的類型

發佈了79 篇原創文章 · 獲贊 17 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章