天天看點

平面陰影技術 DirectX例子 圖形學2

平面陰影雖然不能滿足所有的陰影要求,但是如果是相對簡單的場景的話,用平面陰影也是個不錯的選擇,它的優點是速度快,并不需占用多少運算時間。

步驟如下:

1. 定位需要渲染陰影的物體,和陰影所在平面

2. 計算出平面上陰影的物體定點,定義一定透明度,如50%透明度

3. 渲染

一、首先解決問題是如何計算

方法就是利用向量代數和空間解析幾何的知識,知道了光照的方向向量,物體定點,和平面位置,就可以計算了。這裡就不給出具體的計算了,基礎不好的惡補下,這裡主要講圖形學的技術,而且大多數的API,如DirectX和OpenGL都有現場的API去自動計算,記得目的就是為了定義出在平面上的陰影的定點。圖形學中一般的做法就是先計算出這個轉變向量,然後利用這個轉變向量,把原物體轉換成陰影物體,就可以把陰影渲染在平面上了。

而在DirectX中就是可以調用現成的API生成所需要的矩陣:

D3DXMATRIX *D3DXMatrixShadow(
    D3DXMATRIX *pOut,              //我們所需要的矩陣
    CONST D3DXVECTOR4 *pLight,     // 光源
    CONST D3DXPLANE *pPlane        // 陰影所在的平面
);
           

利用Stencil 緩沖技術來防止多重Blending

我們畫陰影需要再一個平面上畫的,但是原圖形是立體的,那麼會發生多個幾何圖形重疊的情況,比如一個物體的正反面重疊子一起,那麼通過多次畫圖就會使得陰影物體更加黑。為了得到正确明暗程度的陰影就要用到Stencil技術了。當然如果不用也可以畫出來,不過就是陰影部分更加黑罷了。

具體做法就是:我們把陰影部分标記到stencil buffer中,然後如果第二次再次寫像素到同樣的陰影部分的時候就拒絕寫入,也就是stencil test會失敗。(當然程式還是正常運作的)。這樣的話,我們就隻畫了一次陰影部分。

Direct3D代碼示例:

1.參數設定

gd3dDevice->SetRenderState(D3DRS_STENCILENABLE, true);//開啟Stencil buffer
HR(gd3dDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);//設定stencil測試函數為等于函數
HR(gd3dDevice->SetRenderState(D3DRS_STENCILREF, 0x0);//設定ref的值為0
HR(gd3dDevice->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);//Mask蒙蔽層的值為1
HR(gd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);//寫入蒙蔽層也為1
HR(gd3dDevice->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);//stencil測試失敗則保持原有的顔色
HR(gd3dDevice->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
HR(gd3dDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR);//這要設定,已經寫入一次的像素位置就不能再次寫入其他像素了
           

2. 計算和定位陰影

// Position shadow.
D3DXVECTOR4 lightDirection(0.577f, -0.577f, 0.577f, 0.0f);
D3DXPLANE groundPlane(0.0f, -1.0f, 0.0f, 0.0f);

D3DXMATRIX S;
D3DXMatrixShadow(&S, &lightDirection, &groundPlane);//這裡得出陰影的轉換矩陣

// Offset the shadow up slightly so that there is no
// z-fighting with the shadow and ground.
D3DXMATRIX eps;
D3DXMatrixTranslation(&eps, 0.0f, 0.001f, 0.0f);//這裡是Z-fighting技術,簡單的來說就是當兩個物體(這裡是指陰影和平面)的深度測試(depth testing)發生重複的時候,就可能會出問題,例如像素會閃爍,這個時候,可以稍微把其中一個物體(這裡是陰影)的位置移一點點。但是據我的實驗可知,問題不會很大,還是可以正常顯示的,無閃爍,不過為了安全起見,那麼移動一下吧。
// Save the original teapot world matrix.
D3DXMATRIX oldTeapotWorld = mTeapotWorld;

// Add shadow projection transform.
mTeapotWorld = mTeapotWorld * S * eps;//這裡進行正式轉換
 
           

3. 最後我們設定顔色,黑色,透明度50%,然後恢複原來的參數設定:

// Alpha blend the shadow.
gd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);//開啟Alpha blending
gd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA));
gd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

drawTeapot();//這裡是畫一個茶壺

// Restore settings.
mTeapotWorld = oldTeapotWorld;
gd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
gd3dDevice->SetRenderState(D3DRS_STENCILENABLE, false);
           

總結:

這裡是平面陰影技術,如果要做複雜的陰影的話,比如水面上的,和亂石中的陰影那就要用更加深的技術了,需要更多的知識,後續在繼續貼出來吧。

Reference:

Introduction to 3D Game Programming with DirectX9.0C Shader Approach (DirectX 的 龍書 封面是條紅龍的)