直到現(xiàn)在我才知道前邊講的所謂黑暗映射之類的是什么東西,其實說白了就是使用多重紋理的一種方式。
多重紋理從原則上講全部可以被多次渲染所替代,因為多重紋理實際上就是將多次渲染中的每遍中的紋理在一遍中進(jìn)行操作。因此我們首先介紹一下多次渲染,然后概要介紹一下多重紋理的使用方法和實現(xiàn)的效果。
一種復(fù)雜的效果往往是無法通過單次渲染來完成的,因此多次渲染實際上非常普遍。在shadow volume算法中就要通過多次渲染來實現(xiàn)陰影混合。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)
這對硬件的性能要求非常高,即使再現(xiàn)在來講對于復(fù)雜場景來講也是不能忍受的。那么為了減少渲染次數(shù),顯卡開始支持多重紋理。
我們來看一下多次渲染和多重紋理的代碼比較。
//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);
通過對比可以看到多重紋理只對場景頂點進(jìn)行了一次繪制,而多次渲染要進(jìn)行對此繪制。
上邊代碼也基本展示了多重紋理的使用方法:設(shè)置每一個紋理階段的紋理操作參數(shù)和操作類型,然后調(diào)用頂點繪制即可。
在一個紋理階段有最多三個紋理參數(shù),通過設(shè)置的某種操作將結(jié)果投入下一個階段。
這樣進(jìn)行多重紋理級聯(lián)以后的效果如下:
具體來說,紋理的操作包括:
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來進(jìn)行設(shè)置。
對于每個顏色的操作最多有三個參數(shù),也是通過 SetTextureStageState()來設(shè)置的,該方法的第一個參數(shù)設(shè)置了紋理階段,第二個參數(shù)設(shè)置了參數(shù)序號: D3DTSS_COLORARG0,D3DTSS_COLORARG1,D3DTSS_COLORARG2。第三個參數(shù)是參數(shù)值:
D3DTA_CURRENT
D3DTA_DIFFUSE
D3DTA_SELECTMASK
D3DTA_SPECULAR
D3DTA_TEMP
D3DTA_TEXTURE
D3DTA_TFACTOR
分別表示了不同的顏色獲取途徑。
OK,下面我們來看一下幾種多重紋理的效果。
1,單色紋理貼圖
一些老的三維加速卡不支持使用目標(biāo)像素的阿爾法值進(jìn)行紋理混合,更多信息請參閱阿爾法紋理混合。一般來說這些加速卡也不支持多重紋理混合,如果應(yīng)用程序在此類適配器上運(yùn)行,那么可以用多趟紋理混合進(jìn)行單色光照貼圖
要進(jìn)行單色光照貼圖,應(yīng)用程序應(yīng)該把光照信息存放在光照貼圖的阿爾法數(shù)據(jù)中。應(yīng)用程序使用Microsoft? Direct3D?的紋理過濾功能把圖元的圖像中的每個像素映射到光照貼圖中的相應(yīng)texel。應(yīng)用程序應(yīng)該把源混合因子設(shè)為相應(yīng)texel的阿爾法值.
以下C++示例代碼描述了應(yīng)用程序如何把一張紋理用作單色光照貼圖。
//本例假設(shè)d3dDevice為指向IDirect3DDevice9接口的有效指針,
//且lptexLightMap為指向包含單色光照貼圖數(shù)據(jù)的紋理的有效指針。
//把光照貼圖設(shè)置為當(dāng)前紋理。
d3dDevice->SetTexture(0, lptexLightMap);
//設(shè)置顏色操作。
d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
//設(shè)置顏色操作的第一個參數(shù)。
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1,D3DTA_TEXTURE | D3DTA_ALPHAREPLICATE);
因為不支持目標(biāo)阿爾法混合的適配器一般來說也不支持多重紋理混合,這個示例把光照貼圖設(shè)為第一張紋理,而這在所有三維加速卡上都是可用的。示例代碼先設(shè)置紋理混合層的顏色操作,讓紋理數(shù)據(jù)與圖元已有的顏色進(jìn)行混合,然后選擇第一張紋理和圖元已有的顏色作為輸入數(shù)據(jù)。
2,有色光照貼圖
如果應(yīng)用程序使用有色光照貼圖,那么通常會渲染得到更具真實感的三維場景。一張有色光照貼圖使用RGB數(shù)據(jù)存放光照信息。
以下C++示例代碼顯示了如何用RGB顏色數(shù)據(jù)進(jìn)行光照貼圖。
//本例假設(shè)d3dDevice為指向IDirect3DDevice9接口的有效指針,
//且lptexLightMap為指向包含單色光照貼圖數(shù)據(jù)的紋理的有效指針。
//把光照貼圖設(shè)為第一張紋理。
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 );
這個示例先把光照貼圖設(shè)為第一張紋理,然后設(shè)置第一個混合層的狀態(tài),對輸入數(shù)據(jù)進(jìn)行調(diào)制(相乘),并把第一張紋理和圖元的當(dāng)前顏色用作調(diào)制操作的參數(shù)。
3,鏡面反射貼圖
在對發(fā)亮的物體——那些使用了高反射度材質(zhì)的物體——進(jìn)行光照計算時會產(chǎn)生鏡面反射高光。在一些情況下,由光照模塊產(chǎn)生的鏡面反射高光不夠精確,為了產(chǎn)生更吸引人的鏡面反射高光,許多Microsoft? Direct3D?應(yīng)用程序會給圖元使用鏡面反射光照貼圖。
要進(jìn)行鏡面反射光照貼圖,只需把鏡面反射光照貼圖與圖元的紋理相加,然后再和RGB光照貼圖進(jìn)行調(diào)制(與結(jié)果相乘)操作。
//以下C++示例代碼描述了這個過程。
//本例假設(shè)d3dDevice為指向IDirect3DDevice9接口的有效指針
//lptexBaseTexture為指向紋理的有效指針。
//lptexSpecLightMap為指向包含RGB鏡面反射光照貼圖數(shù)據(jù)的紋理的有效指針。
//lptexLightMap為指向包含RGB光照貼圖數(shù)據(jù)的紋理的有效指針。
//設(shè)置基本紋理。
d3dDevice->SetTexture(0, lptexBaseTexture );
//設(shè)置要對基本紋理執(zhí)行的操作及參數(shù)。
d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
//設(shè)置鏡面反射光照貼圖。
d3dDevice->SetTexture(1, lptexSpecLightMap);
//設(shè)置要對鏡面反射光照貼圖執(zhí)行的操作及參數(shù)。
d3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_ADD );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT );
//設(shè)置RGB光照貼圖。
d3dDevice->SetTexture(2, lptexLightMap);
//設(shè)置要對RGB光照貼圖執(zhí)行的操作及參數(shù)。
d3dDevice->SetTextureStageState(2,D3DTSS_COLOROP, D3DTOP_MODULATE);
d3dDevice->SetTextureStageState(2,D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(2,D3DTSS_COLORARG2, D3DTA_CURRENT );
4,漫反射光照貼圖
不光滑的表面在被光源照射時會顯示漫反射光。漫反射光的亮度取決于表面到光源的距離及表面法向與光源的方向向量間的夾角。通過光照計算(譯注:由光照模塊執(zhí)行)模擬漫反射光只能得到一般的效果。
應(yīng)用程序可以用光照貼圖模擬更為復(fù)雜的漫反射光照效果,只需在基本紋理的基礎(chǔ)上再加一張漫反射光照貼圖即可,如以下C++示例代碼所示。
//本例假設(shè)d3dDevice為指向IDirect3DDevice9接口的有效指針。
//lptexBaseTexture為指向紋理的有效指針。
//lptexLightMap為指向包含RGB光照貼圖數(shù)據(jù)的紋理的有效指針。
//設(shè)置基本紋理。
d3dDevice->SetTexture(0,lptexBaseTexture );
//設(shè)置要對基本紋理執(zhí)行的操作及參數(shù)。
d3dDevice->SetTextureStageState(0,D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_DIFFUSE );
//設(shè)置漫反射光照貼圖。
d3dDevice->SetTexture(1,lptexDiffuseLightMap );
//設(shè)置混合層 。
d3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT );
上邊介紹了四種基本貼圖,下邊介紹幾種特效貼圖。
1,黑暗映射(Dark mapping)
黑暗映射實際上是把一張灰度圖作用于當(dāng)前的紋理中。采用調(diào)制的方式進(jìn)行混合,適合于微弱光照的情況進(jìn)行渲染。
2,黑暗貼圖動畫
就是通過控制顯示的次數(shù)和顯示亮度的關(guān)系,分別調(diào)用三種不同的調(diào)制操作類型來交替顯示,就可以達(dá)到黑暗貼圖動畫的效果。適合于閃爍的黑暗環(huán)境。
3,細(xì)節(jié)紋理(Detail mapping)
采用一張細(xì)節(jié)紋理,將基礎(chǔ)紋理和細(xì)節(jié)紋理做addsigned操作,可以模擬粗糙的表面細(xì)節(jié)。
上述效果都不是固定的應(yīng)用,可以盡可能多的采用紋理類型和紋理混合形式,最終實現(xiàn)更好的效果。
順便說一下alpha調(diào)制。
原來我一直以為作為第四維alpha和RGB的處理方式是一樣的,直到昨天問了大牛才知道不是。現(xiàn)在的圖形硬件已經(jīng)可以對RGB和alpha分別進(jìn)行處理,因為alpha最開始是用來表示透明的,又可以用來在混合的時候表示混合因子,但是隨著圖形技術(shù)的發(fā)展alpha的應(yīng)用也發(fā)生了變化,于是圖形硬件開始允許alpha進(jìn)行單獨的操作。那么d3d里邊就可以對alpha操作進(jìn)行單獨的設(shè)置。alpha操作的設(shè)置方法同顏色操作的設(shè)置方式基本相同,操作類型甚至都差不多。
最后是紋理管理。d3d有一個時間戳管理方法,通過最近最少使用算法來進(jìn)行從內(nèi)存到顯存的調(diào)度。當(dāng)然也允許設(shè)置紋理的優(yōu)先級來強(qiáng)制管理。