直到現在我才知道前邊講的所謂黑暗映射之類的是什么東西,其實說白了就是使用多重紋理的一種方式。
多重紋理從原則上講全部可以被多次渲染所替代,因為多重紋理實際上就是將多次渲染中的每遍中的紋理在一遍中進行操作。因此我們首先介紹一下多次渲染,然后概要介紹一下多重紋理的使用方法和實現的效果。
一種復雜的效果往往是無法通過單次渲染來完成的,因此多次渲染實際上非常普遍。在shadow volume算法中就要通過多次渲染來實現陰影混合。Hook在siggraph1998中提到Quake3使用了10次渲染:
1~4 累積凹凸貼圖(Accumulate bump map)
5 漫反射光照(Diffuse lighting)
6 基本紋理,具有鏡面反射成分
7 鏡面光照(Specular lighting)
8 放射性光照(Emissive lighting)
9 體積/大氣效果(volumetric/atmosphere effect)
10 屏幕顯示(screen flash)
這對硬件的性能要求非常高,即使再現在來講對于復雜場景來講也是不能忍受的。那么為了減少渲染次數,顯卡開始支持多重紋理。
我們來看一下多次渲染和多重紋理的代碼比較。
//texture #1
m_pd3dDevice->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
m_pd3dDevice->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1);
m_pd3dDevice->SetTexture(0,m_pWallTexture);
m_pd3dDevice->SetSteamSource(0,m_pCubeVB,0,sizeof(CUBEVERTEXT));
m_pd3dDevice->SetFVF(FVF_CUBEVERTEXT);
m_pd3dDevice->SetIndices(m_pCubeIB,0);
m_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,24,0,36/3);
//texture #2
m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE);
m_pd3dDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_ONE);
m_pd3dDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);
m_pd3dDevice->SetTexture(0,m_pDetailTexture);
m_pd3dDevice->SetSteamSource(0,m_pCubeVB,0,sizeof(CUBEVERTEXT));
m_pd3dDevice->SetFVF(FVF_CUBEVERTEXT);
m_pd3dDevice->SetIndices(m_pCubeIB);
m_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,24,0,36/3);
//texture #1
m_pd3dDevice->SetTexture(0,m_pWallTexture);
m_pd3dDevice->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
m_pd3dDevice->SetTextureStageState(0,D3DTSS_COLOROP,D3DTOP_SELECTARG1);
//texture #2
m_pd3dDevice->SetTexture(1,m_pDetailTexture);
m_pd3dDevice->SetTextureStageState(1,D3DTSS_TEXCOORDINDEX,0);
m_pd3dDevice->SetTextureStageState(0,D3DTSS_COLORARG1,D3DTA_TEXTURE);
m_pd3dDevice->SetTextureStageState(0,D3DTSS_COLORARG2,D3DTA_CURRENT);
m_pd3dDevice->SetSteamSource(0,m_pCubeVB,0,sizeof(CUBEVERTEXT));
m_pd3dDevice->SetFVF(FVF_CUBEVERTEXT);
m_pd3dDevice->SetIndices(m_pCubeIB);
m_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,24,0,36/3);
通過對比可以看到多重紋理只對場景頂點進行了一次繪制,而多次渲染要進行對此繪制。
上邊代碼也基本展示了多重紋理的使用方法:設置每一個紋理階段的紋理操作參數和操作類型,然后調用頂點繪制即可。
在一個紋理階段有最多三個紋理參數,通過設置的某種操作將結果投入下一個階段。
這樣進行多重紋理級聯以后的效果如下:
具體來說,紋理的操作包括:
typedef enum _D3DTEXTUREOP {
D3DTOP_DISABLE = 1,
D3DTOP_SELECTARG1 = 2,
D3DTOP_SELECTARG2 = 3,
D3DTOP_MODULATE = 4,
D3DTOP_MODULATE2X = 5,
D3DTOP_MODULATE4X = 6,
D3DTOP_ADD = 7,
D3DTOP_ADDSIGNED = 8,
D3DTOP_ADDSIGNED2X = 9,
D3DTOP_SUBTRACT = 10,
D3DTOP_ADDSMOOTH = 11,
D3DTOP_BLENDDIFFUSEALPHA = 12,
D3DTOP_BLENDTEXTUREALPHA = 13,
D3DTOP_BLENDFACTORALPHA = 14,
D3DTOP_BLENDTEXTUREALPHAPM = 15,
D3DTOP_BLENDCURRENTALPHA = 16,
D3DTOP_PREMODULATE = 17,
D3DTOP_MODULATEALPHA_ADDCOLOR = 18,
D3DTOP_MODULATECOLOR_ADDALPHA = 19,
D3DTOP_MODULATEINVALPHA_ADDCOLOR = 20,
D3DTOP_MODULATEINVCOLOR_ADDALPHA = 21,
D3DTOP_BUMPENVMAP = 22,
D3DTOP_BUMPENVMAPLUMINANCE = 23,
D3DTOP_DOTPRODUCT3 = 24,
D3DTOP_MULTIPLYADD = 25,
D3DTOP_LERP = 26,
D3DTOP_FORCE_DWORD = 0x7fffffff
} D3DTEXTUREOP;
這里邊也包括了alpha混合的操作,都是通過D3DTEXTUREOP來進行設置。
對于每個顏色的操作最多有三個參數,也是通過 SetTextureStageState()來設置的,該方法的第一個參數設置了紋理階段,第二個參數設置了參數序號: D3DTSS_COLORARG0,D3DTSS_COLORARG1,D3DTSS_COLORARG2。第三個參數是參數值:
D3DTA_CURRENT
D3DTA_DIFFUSE
D3DTA_SELECTMASK
D3DTA_SPECULAR
D3DTA_TEMP
D3DTA_TEXTURE
D3DTA_TFACTOR
分別表示了不同的顏色獲取途徑。
OK,下面我們來看一下幾種多重紋理的效果。
1,單色紋理貼圖
一些老的三維加速卡不支持使用目標像素的阿爾法值進行紋理混合,更多信息請參閱阿爾法紋理混合。一般來說這些加速卡也不支持多重紋理混合,如果應用程序在此類適配器上運行,那么可以用多趟紋理混合進行單色光照貼圖
要進行單色光照貼圖,應用程序應該把光照信息存放在光照貼圖的阿爾法數據中。應用程序使用Microsoft? Direct3D?的紋理過濾功能把圖元的圖像中的每個像素映射到光照貼圖中的相應texel。應用程序應該把源混合因子設為相應texel的阿爾法值.
以下C++示例代碼描述了應用程序如何把一張紋理用作單色光照貼圖。
//本例假設d3dDevice為指向IDirect3DDevice9接口的有效指針,
//且lptexLightMap為指向包含單色光照貼圖數據的紋理的有效指針。
//把光照貼圖設置為當前紋理。
d3dDevice->SetTexture(0, lptexLightMap);
//設置顏色操作。
d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
//設置顏色操作的第一個參數。
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1,D3DTA_TEXTURE | D3DTA_ALPHAREPLICATE);
因為不支持目標阿爾法混合的適配器一般來說也不支持多重紋理混合,這個示例把光照貼圖設為第一張紋理,而這在所有三維加速卡上都是可用的。示例代碼先設置紋理混合層的顏色操作,讓紋理數據與圖元已有的顏色進行混合,然后選擇第一張紋理和圖元已有的顏色作為輸入數據。
2,有色光照貼圖
如果應用程序使用有色光照貼圖,那么通常會渲染得到更具真實感的三維場景。一張有色光照貼圖使用RGB數據存放光照信息。
以下C++示例代碼顯示了如何用RGB顏色數據進行光照貼圖。
//本例假設d3dDevice為指向IDirect3DDevice9接口的有效指針,
//且lptexLightMap為指向包含單色光照貼圖數據的紋理的有效指針。
//把光照貼圖設為第一張紋理。
d3dDevice->SetTexture(0, lptexLightMap);
d3dDevice->SetTextureStageState(0,D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState( 0,D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_DIFFUSE );
這個示例先把光照貼圖設為第一張紋理,然后設置第一個混合層的狀態,對輸入數據進行調制(相乘),并把第一張紋理和圖元的當前顏色用作調制操作的參數。
3,鏡面反射貼圖
在對發亮的物體——那些使用了高反射度材質的物體——進行光照計算時會產生鏡面反射高光。在一些情況下,由光照模塊產生的鏡面反射高光不夠精確,為了產生更吸引人的鏡面反射高光,許多Microsoft? Direct3D?應用程序會給圖元使用鏡面反射光照貼圖。
要進行鏡面反射光照貼圖,只需把鏡面反射光照貼圖與圖元的紋理相加,然后再和RGB光照貼圖進行調制(與結果相乘)操作。
//以下C++示例代碼描述了這個過程。
//本例假設d3dDevice為指向IDirect3DDevice9接口的有效指針
//lptexBaseTexture為指向紋理的有效指針。
//lptexSpecLightMap為指向包含RGB鏡面反射光照貼圖數據的紋理的有效指針。
//lptexLightMap為指向包含RGB光照貼圖數據的紋理的有效指針。
//設置基本紋理。
d3dDevice->SetTexture(0, lptexBaseTexture );
//設置要對基本紋理執行的操作及參數。
d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
//設置鏡面反射光照貼圖。
d3dDevice->SetTexture(1, lptexSpecLightMap);
//設置要對鏡面反射光照貼圖執行的操作及參數。
d3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_ADD );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT );
//設置RGB光照貼圖。
d3dDevice->SetTexture(2, lptexLightMap);
//設置要對RGB光照貼圖執行的操作及參數。
d3dDevice->SetTextureStageState(2,D3DTSS_COLOROP, D3DTOP_MODULATE);
d3dDevice->SetTextureStageState(2,D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(2,D3DTSS_COLORARG2, D3DTA_CURRENT );
4,漫反射光照貼圖
不光滑的表面在被光源照射時會顯示漫反射光。漫反射光的亮度取決于表面到光源的距離及表面法向與光源的方向向量間的夾角。通過光照計算(譯注:由光照模塊執行)模擬漫反射光只能得到一般的效果。
應用程序可以用光照貼圖模擬更為復雜的漫反射光照效果,只需在基本紋理的基礎上再加一張漫反射光照貼圖即可,如以下C++示例代碼所示。
//本例假設d3dDevice為指向IDirect3DDevice9接口的有效指針。
//lptexBaseTexture為指向紋理的有效指針。
//lptexLightMap為指向包含RGB光照貼圖數據的紋理的有效指針。
//設置基本紋理。
d3dDevice->SetTexture(0,lptexBaseTexture );
//設置要對基本紋理執行的操作及參數。
d3dDevice->SetTextureStageState(0,D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_DIFFUSE );
//設置漫反射光照貼圖。
d3dDevice->SetTexture(1,lptexDiffuseLightMap );
//設置混合層 。
d3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT );
上邊介紹了四種基本貼圖,下邊介紹幾種特效貼圖。
1,黑暗映射(Dark mapping)
黑暗映射實際上是把一張灰度圖作用于當前的紋理中。采用調制的方式進行混合,適合于微弱光照的情況進行渲染。
2,黑暗貼圖動畫
就是通過控制顯示的次數和顯示亮度的關系,分別調用三種不同的調制操作類型來交替顯示,就可以達到黑暗貼圖動畫的效果。適合于閃爍的黑暗環境。
3,細節紋理(Detail mapping)
采用一張細節紋理,將基礎紋理和細節紋理做addsigned操作,可以模擬粗糙的表面細節。
上述效果都不是固定的應用,可以盡可能多的采用紋理類型和紋理混合形式,最終實現更好的效果。
順便說一下alpha調制。
原來我一直以為作為第四維alpha和RGB的處理方式是一樣的,直到昨天問了大牛才知道不是。現在的圖形硬件已經可以對RGB和alpha分別進行處理,因為alpha最開始是用來表示透明的,又可以用來在混合的時候表示混合因子,但是隨著圖形技術的發展alpha的應用也發生了變化,于是圖形硬件開始允許alpha進行單獨的操作。那么d3d里邊就可以對alpha操作進行單獨的設置。alpha操作的設置方法同顏色操作的設置方式基本相同,操作類型甚至都差不多。
最后是紋理管理。d3d有一個時間戳管理方法,通過最近最少使用算法來進行從內存到顯存的調度。當然也允許設置紋理的優先級來強制管理。