像素著色器是一段執(zhí)行在圖形卡的GPU上的程序,它運(yùn)行在對每個(gè)像素進(jìn)行光柵化處理時(shí)。(不像頂點(diǎn)著色器,Direct3D不會(huì)以軟件模擬像素著色器的功能。)實(shí)際上它替換了固定功能管線的多紋理化階段(the
multitexturing stage),并賦予我們直接操縱單獨(dú)的像素和訪問每個(gè)像素的紋理坐標(biāo)的能力。這種對像素和紋理坐標(biāo)的直接訪問使我們可以達(dá)成各種特效,例如:多紋理化(multitexturing)、每像素光照(per
pixel lighting)、景深(depth of field)、云狀物模擬(cloud
simulation)、焰火模擬(fire simulation)、高級陰影技術(shù)(sophisticated
shadowing technique)。
圖形卡支持的像素著色器的版本可以通過D3DCAPS9結(jié)構(gòu)的PixelShaderVersion成員和D3DPS_VERSION宏進(jìn)行檢查。下列代碼片斷展示了這點(diǎn):
// If the
device's supported version is less than version 2.0
if(
caps.PixelShaderVersion < D3DPS_VERSION(2, 0) )
// Then pixel shader version 2.0 is not
supported on this device.
|
多紋理化(Multitexturing)可能是用像素著色器實(shí)現(xiàn)的最簡單的技巧了。
多紋理化后面的概念有一點(diǎn)和混合(blending)相關(guān),可以將正要被光柵化的像素與之前寫入后臺(tái)緩沖的像素進(jìn)行混合來達(dá)成一種特效。我們延伸這種相同的思想到多紋理化中(multiple
texture)。也就是說,我們一次使用幾個(gè)紋理,然后定義這些紋理如何被混合在一起,以達(dá)到一種特殊效果。多紋理化的一個(gè)通常的用法是執(zhí)行光照。作為在頂點(diǎn)處理階段使用Direct3D的光照模型的替代,我們使用一種叫做“光照圖”(light
map)的特殊紋理貼圖(texture map),它編碼(encode)表面是如何被光照的。例如,假設(shè)我們希望一盞聚光燈(spotlight)照在一個(gè)大木箱上,我們要么定義一個(gè)D3DLIGHT9結(jié)構(gòu)的聚光燈,要么將代表木箱的紋理貼圖與代表聚光燈的光照映射混合在一起,如圖18.1所示。

注意:結(jié)果圖像依賴于紋理被混合的方式。在固定功能管線的多紋理化階段,混合方程式被紋理渲染狀態(tài)(texture render
state)控制。用像素著色器,我們能以可編程的方式在代碼中寫出混合函數(shù)的簡單表達(dá)式。這使我們可以用任何我們想要的方式混合紋理。
混合多個(gè)紋理(本例中是兩個(gè))來照亮木箱比起Direct3D的光照來有兩個(gè)好處:
光照是是預(yù)先在聚光燈的光照貼圖里計(jì)算好的。因此,光照不需要在運(yùn)行時(shí)被計(jì)算,這節(jié)省了處理時(shí)間。當(dāng)然,只有靜態(tài)對象和靜態(tài)燈光的光照可以被預(yù)先計(jì)算。
因?yàn)楣庹請D是預(yù)先計(jì)算好的,我們能夠使用比Direct3D的(光照)模型多的多的更加精確的和成熟的光照模型。(更好的光照可以產(chǎn)生更真實(shí)的場景。)
備注:多紋理化階段的典型應(yīng)用是實(shí)現(xiàn)靜態(tài)對象的完全光照引擎(full lighting engine)。例如,我們可以用一個(gè)紋理貼圖保存對象的顏色,比如木箱的紋理貼圖。然后我們可以用一個(gè)散射光照貼圖(diffuse
light map)保存散射表面著色(diffuse surface shade),一個(gè)單獨(dú)的鏡面光照貼圖保存鏡面表面著色,一個(gè)霧狀物貼圖(fog
map)保存覆蓋在表面的霧狀物的總量,還有可以用一個(gè)細(xì)節(jié)貼圖(detail map)保存小的、高訪問率的表面的細(xì)節(jié)。當(dāng)所有這些紋理被組合起來,只需到這些預(yù)先計(jì)算的紋理中檢索,就可以有效的照亮、著色并且增加細(xì)節(jié)到場景中去。
注意:聚光燈光照貼圖在很基礎(chǔ)的光照貼圖中是一個(gè)價(jià)值不高(trivial)的例子。一般的的程序通過給定的場景和光源來生成光照貼圖。
18.1.1
允許多個(gè)紋理
回憶一下,紋理是用IDirect3DDevice9::SetTexture方法設(shè)置,而采樣器狀態(tài)(sampler
state)是用IDirect3DDevice9::SetSamplerState方法設(shè)置,原型如下:
HRESULT IDirect3DDevice9::SetTexture(
DWORD Stage, // specifies the texture stage
index
IDirect3DBaseTexture9 *pTexture
);
HRESULT IDirect3DDevice9::SetSamplerState(
DWORD Sampler, // specifies the sampler stage
index
D3DSAMPLERSTATETYPE Type,
DWORD Value
);
|
注意:一個(gè)特定的采樣器階段索引I關(guān)聯(lián)第i個(gè)紋理階段(texture
stage)。即第i個(gè)采樣器階段指定采樣器狀態(tài)是第i集(set)紋理。
紋理/采樣器階段索引標(biāo)識(shí)了我們希望設(shè)置的紋理/采樣器的紋理/采樣器階段。因此,我們可以允許多個(gè)紋理并通過使用不同的階段索引設(shè)置其相應(yīng)的采樣器狀態(tài)。例如,假設(shè)我們要允許三個(gè)紋理,我們像這樣使用階段0,1和2:
// Set first
texture and corresponding sampler states.
Device->SetTexture(0, Tex1);
Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
// Set second
texture and corresponding sampler states.
Device->SetTexture(1, Tex2);
Device->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
Device->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
Device->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
// Set third
texture and corresponding sampler states.
Device->SetTexture(2, Tex3);
Device->SetSamplerState(2, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
Device->SetSamplerState(2, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
Device->SetSamplerState(2, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
|
這段代碼使用Tex1,
Tex2和Tex3,并設(shè)置每個(gè)紋理的過濾模式。
18.1.2
多紋理坐標(biāo)
對于每個(gè)3D三角形,我們應(yīng)該在紋理上定義一個(gè)三角形以映射該3D三角形。我們通過對每個(gè)頂點(diǎn)增加紋理坐標(biāo)完成映射。因此,每三個(gè)頂點(diǎn)定義一個(gè)三角形,它對應(yīng)于紋理上的三角形。
現(xiàn)在使用多紋理,每三個(gè)頂點(diǎn)定義一個(gè)三角形,我們需要在每個(gè)被使用的紋理上定義一個(gè)相應(yīng)的三角形。通過給每個(gè)頂點(diǎn)增加額外的一套紋理坐標(biāo)——每個(gè)頂點(diǎn)一套,對應(yīng)于每個(gè)使用的紋理。舉個(gè)例子,如果我們混合三個(gè)紋理到一起,那么每個(gè)頂點(diǎn)必須有三套紋理坐標(biāo)以索引到三個(gè)使用的紋理。因此,一個(gè)包含三個(gè)紋理的多紋理化頂點(diǎn)結(jié)構(gòu)看起來可能像這樣:
struct
MultiTexVertex
{
MultiTexVertex(float x, float y, float z,
float u0, float v0,
float u1, float v1,
float u2, float v2)
{
_x = x; _y = y; _z = z;
_u0 = u0; _v0 = v0;
_u1 = u1; _v1 = v1;
_u2 = u2; _v2 = v2;
}
float _x, _y, _z;
float _u0, _v0; // Texture coordinates for
texture at stage 0.
float _u1, _v1; // Texture coordinates for
texture at stage 1.
float _u2, _v2; // Texture coordinates for
texture at stage 2.
static const DWORD FVF;
};
const
DWORD MultiTexVertex::FVF = D3DFVF_XYZ | D3DFVF_TEX3;
|
注意,指定自由頂點(diǎn)格式標(biāo)記D3DFVF_TEX3表明頂點(diǎn)結(jié)構(gòu)包含3套紋理坐標(biāo)。固定功能管線支持最多8套紋理坐標(biāo)。如果多于8套,你必須使用頂點(diǎn)聲明和可編程頂點(diǎn)管線。
注意:在新版本像素著色器中,我們可以使用一套紋理坐標(biāo)集來索引多個(gè)紋理,并因此消除了對多個(gè)紋理坐標(biāo)的需要。當(dāng)然這得假設(shè)每個(gè)紋理階段使用相同的紋理坐標(biāo)。如果每個(gè)階段的紋理坐標(biāo)不同,則我們?nèi)匀恍枰嗉y理坐標(biāo)。
18.2像素著色器輸入和輸出
有兩樣?xùn)|西要輸入到像素著色器:顏色和紋理坐標(biāo)。兩樣都是以每像素為單位的。
注意:頂點(diǎn)顏色是在圖元的面(face
of primitive)間進(jìn)行插值的。
每個(gè)像素的紋理坐標(biāo)就是簡單的 (u , v)
,它指定了紋理的哪個(gè)圖素被映射到像素上。在輸入到像素著色器前,Direct3D根據(jù)頂點(diǎn)顏色和頂點(diǎn)紋理坐標(biāo),為每個(gè)像素計(jì)算顏色和紋理坐標(biāo)。輸入到像素著色器的顏色和紋理坐標(biāo)的數(shù)值依賴于頂點(diǎn)著色器輸出的顏色和紋理坐標(biāo)的數(shù)值。例如,如果一個(gè)頂點(diǎn)著色器輸出了兩個(gè)顏色和三個(gè)紋理坐標(biāo),那么Direct3D將會(huì)為每個(gè)像素計(jì)算兩個(gè)顏色和三個(gè)紋理坐標(biāo)并且把它們把它們輸入到像素著色器。我們使用帶語意的語法(semantic
syntax)映射輸入顏色和紋理坐標(biāo)進(jìn)我們的著色器程序的變量里。用前面的例子,我們可以這樣寫:
struct
PS_INPUT
{
vector c0 : COLOR0;
vector c1 : COLOR1;
float2 t0 : TEXCOORD0;
float2 t1 : TEXCOORD1;
float2 t2 : TEXCOORD2;
};
|
對于輸出,像素著色器只輸出一個(gè)計(jì)算過的該像素的顏色值:
struct
PS_OUTPUT
{
vector finalPixelColor : COLOR0;
};
|
18.3使用像素著色器的步驟
下面的列表概述了創(chuàng)建和使用像素著色器的必要步驟:
1.
編寫并編譯像素著色器
2.
創(chuàng)建一個(gè)IDirect3DPixelShader9接口來代表基于已編譯代碼的像素著色器
3.
用IDirect3DDevice9::SetPixelShader方法允許該像素著色器
當(dāng)然,用完頂點(diǎn)著色器之后我們必須銷毀它。
18.3.1
編寫并編譯像素著色器
我們用與編譯頂點(diǎn)著色器一樣的方式編譯像素著色器。首先,我們必須編寫一個(gè)像素著色器程序, 我們用HLSL編寫我們的著色器。一旦寫好著色器代碼,我們就可以用D3DXCompileShaderFromFile函數(shù)編譯該著色器了,這個(gè)函數(shù)返回一個(gè)ID3DXBuffer指針,它包含已編譯的著色器代碼。
注意:因?yàn)槲覀兪褂玫氖窍袼刂鳎砸浀冒丫幾g目標(biāo)改成像素著色器目標(biāo)(比如:ps_2_0),而不是頂點(diǎn)著色器目標(biāo)(比如:vs_2_0)。編譯目標(biāo)通過D3DXCompileShaderFromFile函數(shù)的一個(gè)參數(shù)指定。
18.3.2
創(chuàng)建像素著色器
一旦我們編譯了著色器代碼,我們就可以獲得一個(gè)IDirect3DPixelShader的接口指針,它代表一個(gè)像素著色器,使用下面的方法:
HRESULT IDirect3DDevice9::CreatePixelShader(
CONST DWORD *pFunction,
IDirect3DPixelShader9** ppShader
);
|
pFunction——已編譯著色器代碼的指針
ppShader——返回一個(gè)IDirect3DPixelShader9接口的指針
例如,假設(shè)變量shader是一個(gè)包含已編譯著色器代碼的ID3DXBuffer接口指針。那么要獲得IDirect3DPixelShader9接口,我們應(yīng)該寫:
IDirect3DPixelShader9* MultiTexPS = 0;
hr =
Device->CreatePixelShader( (DWORD*)shader->GetBufferPointer(),
&MultiTexPS);
|
注意:重申一遍,D3DXCompileShaderFromFile是一個(gè)可以返回已編譯著色器代碼(shader)的函數(shù)。
18.3.3
建立像素著色器
在我們獲得一個(gè)代表我們的像素著色器的IDirect3DPixelShader9接口的指針之后,我們可以使用下面的方法使用它:
HRESULT IDirect3DDevice9::SetPixelShader(
IDirect3DPixelShader9* pShader
);
|
這個(gè)方法只接受一個(gè)參數(shù),我們通過它傳遞一個(gè)我們希望使用的指向像素著色器的指針。
Device->SetPixelShader(MultiTexPS);
|
18.3.4
銷毀像素著色器
和其它所有Direct3D接口一樣,要清除這些接口,我們必須在使用完畢后調(diào)用它們的Release方法。
d3d::Release<IDirect3DPixelShader9*>(MultiTexPS);
|