yag Site Admin
註冊時間: 2007-05-02 文章: 689
2704.11 果凍幣
|
發表於: 2010-2-6, PM 6:20 星期六 文章主題: 3D遊戲程式設計入門第8章心得-2 |
|
|
前言:此乃補丁文。只講解心得,不提供完整教學,有興趣的人請自行購買此書。
代碼: | 書名:3D遊戲程式設計入門-使用DirectX 9.0實作
作者:Frank D. Luna
譯者:黃聖峰
出版社:博碩文化 |
用模版(Stencil)畫影子
代碼: | void RenderShadow()
{
Device->SetRenderState(D3DRS_STENCILENABLE, true);
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
Device->SetRenderState(D3DRS_STENCILREF, 0x0);
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_INCR); // increment to 1
// position shadow
D3DXVECTOR4 lightDirection(0.707f, -0.707f, 0.707f, 0.0f);
D3DXPLANE groundPlane(0.0f, -1.0f, 0.0f, 0.0f);
D3DXMATRIX S;
D3DXMatrixShadow(
&S,
&lightDirection,
&groundPlane);
D3DXMATRIX T;
D3DXMatrixTranslation(
&T,
TeapotPosition.x,
TeapotPosition.y,
TeapotPosition.z);
D3DXMATRIX W = T * S;
Device->SetTransform(D3DTS_WORLD, &W);
// alpha blend the shadow
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
D3DMATERIAL9 mtrl = d3d::InitMtrl(d3d::BLACK, d3d::BLACK, d3d::BLACK, d3d::BLACK, 0.0f);
mtrl.Diffuse.a = 0.5f; // 50% transparency.
// Disable depth buffer so that z-fighting doesn't occur when we
// render the shadow on top of the floor.
Device->SetRenderState(D3DRS_ZENABLE, false);
Device->SetMaterial(&mtrl);
Device->SetTexture(0, 0);
Teapot->DrawSubset(0);
Device->SetRenderState(D3DRS_ZENABLE, true);
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
Device->SetRenderState(D3DRS_STENCILENABLE, false);
} |
第一行,開啟模版功能,上篇說過了:
代碼: | Device->SetRenderState(D3DRS_STENCILENABLE, true); |
第二行,設定 Ref & Mask == Value & Mask 時通過模版測試,上篇也說過了:
代碼: | Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL); |
三到五行,設定Ref、Mask、WriteMask,一樣說過了:
代碼: | Device->SetRenderState(D3DRS_STENCILREF, 0x0);
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_INCR); |
前兩個都是Keep不多說,最後一個--當模版測試成功時將模版緩衝器裡的值+1
配合上面將Ref設成0,就可以讓影子不至於在重疊的部份被畫兩遍
因為第一遍畫之前,Ref跟Value都是0,因此通過模版測試,畫上影子
第一遍畫完後,Value被+1,因此變成1
第二遍要畫時,Ref為0,Value為1,因此模版測試失敗,便不再畫上影子,以避免多重混合的現象
跟鏡像一樣,影子本身也是要我們自己計算出來
DX有提供了一個計算影子的函式:D3DXMatrixShadow
需要傳入的是光的方向(lightDirection)與呈現影子的平面(groundPlane):
代碼: | D3DXVECTOR4 lightDirection(0.707f, -0.707f, 0.707f, 0.0f);
D3DXPLANE groundPlane(0.0f, -1.0f, 0.0f, 0.0f); |
使用D3DXMatrixShadow函式來計算時,有兩個比較不一般,需要注意的特點:
第一:光不一定要先經過物體才映射到平面上
第二:光的方向向量與平面的法向量間的夾角要小於90度,如果大於90度,則影子不會呈現
而要滿足小於90度這個特點,就不可避免地會造成一個現象 --
光照形成的影子會呈現在物體所在那半面,不一定會呈現在平面的正面(平面的正面朝向即為法向量方向)
像是此範例中,光的方向是從左後上到右前下照射,所以地面的法向量就要是往下的,這樣才能夾角小於90度
但這樣一來,影子就會呈現在平面的背面,因為法向量往下而物體卻在平面上方
另外,我們可以把光的方向跟平面改成:
代碼: | D3DXVECTOR4 lightDirection(-0.707f, 0.707f, -0.707f, 0.0f);
D3DXPLANE groundPlane(0.0f, 1.0f, 0.0f, 0.0f); |
如上,剛好反過來,光從右前下照射到左後上
照常理來說,光先經過平面才照射到物體,物體不可能在平面上留下影子
但是D3DXMatrixShadow函式的第一特點允許我們這樣使用
這會得到跟原設定同樣的影子
以上是傳入平行光給D3DXMatrixShadow函式的狀況
書上有說D3DXMatrixShadow也接受點光源,只要將
代碼: | D3DXVECTOR4 lightDirection(0.707f, -0.707f, 0.707f, 0.0f); |
改成
代碼: | D3DXVECTOR4 lightPosition(0.707f, -0.707f, 0.707f, 1.0f); |
即可
也就是四維向量的最後一維為0.0f時,它是向量,最後一維為1.0f時,它是點(或說是位置座標)
只要設成1.0f,函式自然就會以點光源去計算影子
不過我沒試過點光源的狀況下,以上兩特點是否依然如此,所以請慎用
接著的code是打開混色功能
我們可以從
代碼: | mtrl.Diffuse.a = 0.5f; |
看出,質料(Material)的混色透明度(alpha)是以Diffuse屬性的alpha來計算的
然後我們以下面這行來關閉深度測試
代碼: | Device->SetRenderState(D3DRS_ZENABLE, false); |
因為接著我們就要把影子畫在平面上
而影子跟平面會具有同樣的深度值
這就造成DX不知道該把影子畫在平面前面還是把平面畫在影子前面
這也就是所謂的z值競爭(z-fighting)
把深度測試關閉後,後畫的東西就會覆蓋過先畫的東西
我們就可以看到完整不閃爍的影子了 |
|