電腦遊戲製作開發設計論壇 首頁 電腦遊戲製作開發設計論壇
任何可以在PC上跑的遊戲都可以討論,主要以遊戲之製作開發為主軸,希望讓台灣的遊戲人有個討論、交流、教學、經驗傳承的園地
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

Google
3D遊戲程式設計入門第8章心得-1

 
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫
上一篇主題 :: 下一篇主題  
發表人 內容
yag
Site Admin


註冊時間: 2007-05-02
文章: 688

2673.35 果凍幣

發表發表於: 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是左手座標系,預設是會把逆時針順序畫的三角形剔除掉
因為鏡像的前後顛倒,所以就要反過來把順時針的三角形剔除,這樣才能正確顯示
說是這麼說,不過在這個範例中茶壺是個密封體,所有的背面都是在茶壺的內部
因此即使不設定這行,看起來依然會是正確的

最後三行只是把前面有修改過的設定改回為預設值罷了
回頂端
檢視會員個人資料 發送私人訊息 發送電子郵件
從之前的文章開始顯示:   
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫 所有的時間均為 台灣時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
可以 在這個版面附加檔案
可以 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作