yag Site Admin
註冊時間: 2007-05-02 文章: 689
2704.11 果凍幣
|
發表於: 2010-1-25, PM 9:28 星期一 文章主題: 3D遊戲程式設計入門第8章心得-1 |
|
|
前言:此乃補丁文。只講解心得,不提供完整教學,有興趣的人請自行購買此書。
代碼: | 書名:3D遊戲程式設計入門-使用DirectX 9.0實作
作者:Frank D. Luna
譯者:黃聖峰
出版社:博碩文化 |
用模版(Stencil)畫鏡像
代碼: | void RenderMirror()
{
//
// Draw Mirror quad to stencil buffer ONLY. In this way
// only the stencil bits that correspond to the mirror will
// be on. Therefore, the reflected teapot can only be rendered
// where the stencil bits are turned on, and thus on the mirror
// only.
//
Device->SetRenderState(D3DRS_STENCILENABLE, true);
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
Device->SetRenderState(D3DRS_STENCILREF, 0x1);
Device->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);
Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
Device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);
Device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
// disable writes to the depth and back buffers
Device->SetRenderState(D3DRS_ZWRITEENABLE, false);
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
// draw the mirror to the stencil buffer
Device->SetStreamSource(0, VB, 0, sizeof(Vertex));
Device->SetFVF(Vertex::FVF);
Device->SetMaterial(&MirrorMtrl);
Device->SetTexture(0, MirrorTex);
D3DXMATRIX I;
D3DXMatrixIdentity(&I);
Device->SetTransform(D3DTS_WORLD, &I);
Device->DrawPrimitive(D3DPT_TRIANGLELIST, 18, 2);
// re-enable depth writes
Device->SetRenderState( D3DRS_ZWRITEENABLE, true );
// only draw reflected teapot to the pixels where the mirror
// was drawn to.
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
// position reflection
D3DXMATRIX W, T, R;
D3DXPLANE plane(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
D3DXMatrixReflect(&R, &plane);
D3DXMatrixTranslation(&T,
TeapotPosition.x,
TeapotPosition.y,
TeapotPosition.z);
W = T * R;
// clear depth buffer and blend the reflected teapot with the mirror
Device->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
// Finally, draw the reflected teapot
Device->SetTransform(D3DTS_WORLD, &W);
Device->SetMaterial(&TeapotMtrl);
Device->SetTexture(0, 0);
// reverse cull mode
Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
Teapot->DrawSubset(0);
// Restore render states.
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
Device->SetRenderState( D3DRS_STENCILENABLE, false);
Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
} |
Stencil就是模版,說起來算是一種遮罩,指定什麼區域可以做些什麼行為,什麼區域不行
在d3d裡它是一種「表面(Surface)」,也就是一種緩衝區,簡單說就是一個記憶體區塊,再說白一點,它基本上算是一種陣列,對應螢幕上的每個像素,以其儲存的值來判斷該像素能不能執行「某種功能」(像是畫影子、畫鏡像…等)
只要執行了
代碼: | Device->SetRenderState(D3DRS_STENCILENABLE, true); |
d3d就會在render時對每個像素多做一個模版測試(Stencil Test),以下面這個算式來判斷該像素是否可以執行某種功能:
( Ref & Mask ) FUNC ( Value & Mask )
代碼: | Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); |
這行就是設成上面那個算式的FUNC,不過在這是設成了D3DCMP_ALWAYS,也就是不論如何模版測試都會PASS
所有的FUNC列表如下:
代碼: | D3DCMP_NEVER
D3DCMP_LESS
D3DCMP_EQUAL
D3DCMP_LESSEQUAL
D3DCMP_GREATER
D3DCMP_NOTEQUAL
D3DCMP_GREATEREQUAL
D3DCMP_ALWAYS
|
除了D3DCMP_NEVER是永遠會FAIL、D3DCMP_ALWAYS永遠PASS之外,其他都是一些左式跟右式比誰大誰小的FUNC
左式的Ref由下面這行設定為1
代碼: | Device->SetRenderState(D3DRS_STENCILREF, 0x1); |
左右兩式的Mask都一樣由下面這行設定
代碼: | Device->SetRenderState(D3DRS_STENCILMASK, 0xffffffff); |
設定0xffffffff實際上也就是沒有mask的意思,畢竟不管什麼數值跟0xffffffff做了位元and(&)得到的都會是原值
至於下面這一行則是跟右式的Value有關:
代碼: | Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff); |
在設定了開啟模版功能之後,只要Render 3d物體,就會自動在相對應的模版緩衝器(Stencil Buffer)中寫入Ref & WriteMask的值,這個值也就是模版測試中的Value了
接下來的三行指定了模版測試的結果對應的行為:
代碼: | Device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);
Device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
|
第一行,當render時深度測試失敗時,相對應的模版緩衝器的值不變
第二行,當render時模版測試失敗時,相對應的模版緩衝器的值不變
第三行,當render時深度測試跟模版測試都通過了,那麼就把模版緩衝器的值改寫成Ref & WriteMask的值
所謂的深度測試失敗,就是指render的物體被擋在其他物體的後面看不到
像是這個範例中,我們要render一面鏡子,但當鏡子被茶壺擋住時,那麼模版緩衝器的值當然就要維持不變
別忘了,模版是為了判斷要不要執行「某種功能」,以鏡子來說,就是決定要不要顯示鏡像
如果鏡子都被茶壺擋住了,你還能從茶壺上看到鏡子的鏡像,那就顯得不合理了
當模版功能都設定好後,我們就要來畫鏡子了
不過我們並不是真的要畫出鏡子,只是要透過畫鏡子這個動作,來設定模版緩衝器,使得茶壺可以在鏡子的範圍內呈現鏡像
所以我們要先執行:
代碼: | Device->SetRenderState(D3DRS_ZWRITEENABLE, false); |
來關閉深度緩衝器,以免影響其值
再執行:
代碼: | Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); |
來使得新畫的鏡子是完全透明的,不會顯示出來,也就是說,不會影響後緩衝器
透過以上兩步,就等於我們畫的鏡子只會對模版緩衝器產生影響
然後就是繪製鏡子,因為我們的FUNC是設為ALWAYS,所以模版緩衝器中,鏡子的部份就會全部被設為Ref & WriteMask,也就是0x1 & 0xffffffff,簡單說,就是會被設成1
繪完後,我們要來繪製鏡射後的茶壺,所以再度把深度緩衝器打開:
代碼: | Device->SetRenderState( D3DRS_ZWRITEENABLE, true ); |
接著我們要把模版測試的設定改一下:
代碼: | Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP); |
這樣設定代表當Ref & Mask == Value & Mask時可通過模版測試,而通過後,模版緩衝器的值依然不變,當然,後緩衝器的值跟深度緩衝器的值會改變
那麼,什麼區塊可以通過模版測試呢?我們Ref的值為1,而剛才畫鏡子時,把鏡子區域的Value也設成了1,所以這次畫茶壺鏡像時,唯有鏡子區域上的茶壺鏡像才能通過模版測試,也才會被寫入後緩衝器了
以上只是決定了鏡像出現的地方,而鏡像本身必須我們自己來計算
DX提供了個計算反射的函式,D3DXMatrixReflect,只需要傳入要反射的平面
因為我們的鏡子是畫在xy平面上,所以要反射的平面就是xy平面:
代碼: | D3DXPLANE plane(0.0f, 0.0f, 1.0f, 0.0f); |
此結構四個數值代表ax + by + cz + d = 0這個一般平面方程式的( a, b, c, d )
其中( a, b, c )是代表法向量,而d則是平面上任一點跟原點的距離
將( 0, 0, 1, 0 )傳入方程式可得z = 0,也就是xy平面
因為鏡像一定是在鏡子的後面,所以畫鏡像前我們必須先把深度緩衝器的值給清空,這樣鏡像才不會被鏡子擋住
當然,我們也不用擔心鏡像會反擋在原茶壺之前,因為茶壺的模版緩衝器的值不為1,只有鏡子是1
清空深度緩衝器,也就是將緩衝器裡的所有值設成1,因為深度範圍是0~1,深度為1會被任何東西擋住:
代碼: | Device->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0); |
清空後,鏡像會畫在鏡子上,因此我們需要決定其混合方式:
代碼: | Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO); |
這種設定可以讓鏡像看起來真的像在鏡子裡面
在畫鏡像之前還有一行:
代碼: | Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW); |
這是設定把依順時針順序畫的三角形給剔除掉
DX是左手座標系,預設是會把逆時針順序畫的三角形剔除掉
因為鏡像的前後顛倒,所以就要反過來把順時針的三角形剔除,這樣才能正確顯示
說是這麼說,不過在這個範例中茶壺是個密封體,所有的背面都是在茶壺的內部
因此即使不設定這行,看起來依然會是正確的
最後三行只是把前面有修改過的設定改回為預設值罷了 |
|