目錄
前言
1.HLSL入門
1.1什么是著色器
1.2什么是HLSL
1.3怎么寫HLSL著色器
1.4怎么用HLSL著色器
2.頂點(diǎn)著色器
2.1可編程數(shù)據(jù)流模型
2.2頂點(diǎn)聲明
2.3用頂點(diǎn)著色器實(shí)現(xiàn)漸變動(dòng)畫
3.像素著色器
3.1多紋理化
3.2多紋理效果的像素著色器
3.3應(yīng)用程序
4.HLSL Effect(效果框架)
4.1Effect代碼結(jié)構(gòu)
4.2用Effect實(shí)現(xiàn)多紋理化效果
結(jié)語
參考資料
前言
本教程針對HLSL(High Level Shading Language)初學(xué)者,從應(yīng)用的角度對HLSL、頂點(diǎn)著色器、像素著色器和Effect效果框架進(jìn)行了介紹,教程中去掉了對HLSL語法等一些細(xì)節(jié)內(nèi)容的討論,力求幫助讀者盡可能快地理解HLSL編程的概念,掌握HLSL編程的方法。
教程中部分闡述直接引用了其他文檔,這是因?yàn)檫@些文檔表述之精要,已經(jīng)達(dá)到了不能更改的地步,這里表示感謝。
本文檔版權(quán)為作者所有,非商業(yè)用途可免費(fèi)使用,轉(zhuǎn)載請注明出處。
1.HLSL入門
1.1什么是著色器
DirectX使用管道技術(shù)(pipeline)進(jìn)行圖形渲染,其構(gòu)架如下:
圖1.1 Direct3D Graphics Pipeline
之前我們使用管道的步驟如下:
1. 設(shè)定頂點(diǎn)、圖元、紋理等數(shù)據(jù)信息;
2. 設(shè)定管道狀態(tài)信息;
² 渲染狀態(tài)
通過SetRenderState方法設(shè)定渲染狀態(tài);
另外,使用以下方法設(shè)置變換、材質(zhì)和光照:
SetTransform
SetMaterial
SetLight
LightEnable
² 取樣器狀態(tài)
通過SetSamplerState方法設(shè)定取樣器狀態(tài);
² 紋理層狀態(tài)
通過SetTextureStageState設(shè)定紋理層狀態(tài);
3. 渲染;
這部分交由D3D管道按照之前的設(shè)定自行完成,這部分操作是D3D預(yù)先固定的,所以這種管道技術(shù)被稱為固定功能管道(fixed function pipeline);
固定功能管道給我們編程提供了一定的靈活性,但是仍有很多效果難以通過這種方式實(shí)現(xiàn),比如:
1. 在渲染過程中,我們要求y坐標(biāo)值大于10的頂點(diǎn)要被繪制到坐標(biāo)值(0,0,0)的地方,在之前的固定功能管道中,頂點(diǎn)被繪制的位置是在第1步即被設(shè)定好的,不可能在渲染過程中進(jìn)行改變,所以是不可行的;
2. 謀頂點(diǎn)在紋理貼圖1上映射為點(diǎn)A,在紋理貼圖2上映射為點(diǎn)B,我們要求該頂點(diǎn)顏色由A、B共同決定,即:
定點(diǎn)顏色 = A點(diǎn)色彩值*0.7 + B點(diǎn)色彩值*0.3
這在固定管道編程中也是不可行的。
以上兩個(gè)問題都可以由可編程管道(pragrammable pipeline)來解決。
可編程管線允許用戶自定義一段可以在GPU上執(zhí)行的程序,代替固定管道技術(shù)中的Vertex Processing和Pixel Processing階段(參照圖1.1),從而在使我們在編程中達(dá)到更大的靈活性。其中替換Vertex Processing的部分叫做Vertex Shader(頂點(diǎn)著色器),替換Pixel Proccessing的部分叫做Pixel Shader(像素著色器),這就是我們所說的著色器Shader。
1.2什么是HLSL
Direct8.x中,著色器是通過低級著色匯編語言來編寫的,這樣的程序更像是匯編式的指令集合,由于其效率低、可讀性差、版本限制等缺點(diǎn),迫切要求出現(xiàn)一門更高級的著色語言。到了Direct3D9,HLSL(High Level Shading Language,高級渲染語言)應(yīng)運(yùn)而生了。
HLSL的語法非常類似于C和C++,學(xué)習(xí)起來是很方便的。
1.3怎么寫HLSL著色器
我們可以直接把HLSL著色器代碼作為一長串字符串編寫進(jìn)我們的應(yīng)用程序源文件中,但是,更加方便和模塊化的方法是把著色器的代碼從應(yīng)用程序代碼中分離出來。因此,我們將著色器代碼單獨(dú)保存為文本格式,然后在應(yīng)用程序中使用特定函數(shù)將其加載進(jìn)來。
下面是一個(gè)完整的HLSL著色器程序代碼,我們把它保存在BasicHLSL.txt中。該著色器完成頂點(diǎn)的世界變換、觀察變換和投影變幻,并將頂點(diǎn)顏色設(shè)定為指定的顏色。
//
// BasicHLSL.txt
//
//
// Global variable
//
matrix WVPMatrix;
vector color;
//
// Structures
//
struct VS_INPUT
{
vector position : POSITION;
};
struct VS_OUTPUT
{
vector position : POSITION;
vector color : COLOR;
};
//
// Functions
//
VS_OUTPUT SetColor(VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.position = mul(input.position, WVPMatrix);
output.color = color;
return output;
}
下面就針對上述代碼講解一下HLSL著色器程序的編寫:
1.3.1全局變量
代碼中聲明了兩個(gè)全局變量:
matrix WVPMatrix;
vector color;
變量WVPMatrix是一個(gè)矩陣類型,它包含了世界、觀察、投影的合矩陣,用于對頂點(diǎn)進(jìn)行坐標(biāo)變換;
變量color是一個(gè)向量類型,它用于設(shè)定頂點(diǎn)顏色;
代碼中并沒有對全局變量進(jìn)行初始化,這是因?yàn)槲覀儗θ肿兞康某跏蓟^程將在應(yīng)用程序中進(jìn)行,全局變量在應(yīng)用程序中賦值而在著色器程序中使用,這是應(yīng)用程序和著色器通信的關(guān)鍵所在。具體賦值過程將在后續(xù)部分講述。
1.3.2輸入輸出
² 輸入輸出結(jié)構(gòu)
程序中定義了兩個(gè)輸入輸出結(jié)構(gòu)VS_INPUT和VS_OUTPUT
struct VS_INPUT
{
vector position : POSITION;
};
struct VS_OUTPUT
{
vector position : POSITION;
vector color : COLOR;
};
自定義的結(jié)構(gòu)可以采用任意名稱,結(jié)構(gòu)不過是一種組織數(shù)據(jù)的方式,并不是強(qiáng)制的,你也可以不使用,而將本程序的輸入改為:
vector position : POSITION;
² 標(biāo)志符
用于輸入輸出的變量采用用一種特殊的聲明方式:
Type VariableName : Semantic
這個(gè)特殊的冒號(hào)語法表示一個(gè)語義,冒號(hào)后面的標(biāo)志符用來指定變量的用途,如
vector position : POSITION;
其中,POSITION標(biāo)志符表明該變量表示頂點(diǎn)位置,另外還有諸如COLOR、NORMAL等很多表示其他意義的標(biāo)志符。
本節(jié)所說的輸入輸出其實(shí)是指著色器代碼和編譯器、GPU之間的通信,和應(yīng)用程序是無關(guān)的,所以這些變量不需要在應(yīng)用程序中進(jìn)行賦值,標(biāo)志符告訴編譯器各個(gè)輸入輸出變量的用途(頂點(diǎn)位置、法線、顏色等),這是著色器代碼和編譯器、GPU之間通信的關(guān)鍵。
1.3.3入口函數(shù)
程序中還定義了一個(gè)函數(shù)SetColor:
OUTPUT SetColor(INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.position = mul(input.position, WVPMatrix);
output.color = color;
return output;
}
1. 該函數(shù)以input和output類型作為輸入輸出;
2. 使全局變量WVPMatrix和input.position相乘,以完成頂點(diǎn)的世界、觀察、投影變換,并把結(jié)果賦值到output.position;
output.position = mul(input.position, WVPMatrix);
3. 將全局變量color的值賦給output.color;
output.color = color;
4. 在同一個(gè)著色器代碼文件中,可以有多個(gè)用戶自定義函數(shù),因此在應(yīng)用程序中需要指定一個(gè)入口函數(shù),相當(dāng)于windows程序的WinMain函數(shù),本程序只包含SetColor一個(gè)函數(shù)而且它將被做為入口函數(shù)使用。
1.3.4總結(jié)
至此,一個(gè)HLSL著色器編寫完畢,渲染過程中,當(dāng)一個(gè)頂點(diǎn)被送到著色器時(shí):
1. 全局變量WVPMatrix、color將在應(yīng)用程序中被賦值;
2. 入口函數(shù)SetColor被調(diào)用編譯器根據(jù)標(biāo)志符將頂點(diǎn)信息填充到VS_INPUT中的各個(gè)字段;
3. SetColor函數(shù)中,首先定義一個(gè)VS_OUTPUT信息,之后根據(jù)WVPMatrix和color變量完成頂點(diǎn)的坐標(biāo)變換和顏色設(shè)定操作,最后函數(shù)返回VS_OUTPUT結(jié)構(gòu);
4. 編譯器將會(huì)再次根據(jù)標(biāo)志符把返回的VS_OUTPUT結(jié)構(gòu)中的各字段映射為頂點(diǎn)相應(yīng)的信息。
5. 頂點(diǎn)被送往下一個(gè)流程接受進(jìn)一步處理。
上述過程中,全局變量在應(yīng)用程序中賦值而在著色器程序中使用,這是應(yīng)用程序和著色器通信的關(guān)鍵所在;標(biāo)志符告訴編譯器各個(gè)輸入輸出變量的用途(頂點(diǎn)位置、法線、顏色等),這是著色器代碼和編譯器、GPU之間通信的關(guān)鍵。個(gè)人認(rèn)為這是著色器中最為精義的地方:)
1.4怎么用HLSL著色器
應(yīng)用程序中對HLSL著色器的使用分為以下步驟:
1. 加載(稱為編譯更為妥當(dāng))著色器代碼;
2. 創(chuàng)建(頂點(diǎn)/像素)著色器;
3. 對著色器中的變量進(jìn)行賦值,完成應(yīng)用程序和著色器之間的通信。
4. 把著色器設(shè)定到渲染管道中;
本例使用的著色器是一個(gè)頂點(diǎn)著色器,因此我們將通過頂點(diǎn)著色器的使用來講解著色器的使用過程,像素著色器的使用過程與此大同小異,二者之間僅有些微差別。
1.4.1聲明全局變量
IDirect3DVertexShader9* BasicShader = 0; //頂點(diǎn)著色器指針
ID3DXConstantTable* BasicConstTable = 0; //常量表指針
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ColorHandle = 0;
ID3DXMesh* Teapot = 0; //指向程序中D3D茶壺模型的指針
1.4.2編譯著色器
通過D3DXCompileShaderFromFile函數(shù)從應(yīng)用程序外部的文本文件BasicHLSL.txt中編譯一個(gè)著色器:
//編譯后的著色器代碼將被放在一個(gè)buffer中,可以通過ID3DXBuffer接口對其進(jìn)行訪問,之后的著色器將從這里創(chuàng)建
ID3DXBuffer* shaderBuffer = 0;
//用于接受錯(cuò)誤信息
ID3DXBuffer* errorBuffer = 0;
//編譯著色器代碼
D3DXCompileShaderFromFile("BasicHLSL.txt", //著色器代碼文件名
0,
0,
"SetColor", //入口函數(shù)名稱
"vs_1_1", //頂點(diǎn)著色器版本號(hào)
D3DXSHADER_DEBUG,// Debug模式編譯
&shaderBuffer, //指向編譯后的著色器代碼的指針
&errorBuffer,
&BasicConstTable); //常量表指針
1.4.3創(chuàng)建著色器
應(yīng)用程序通過CreateVertexShader創(chuàng)建一個(gè)頂點(diǎn)著色器,注意使用了上一步得到的shaderBuffer:
g_pd3dDevice->CreateVertexShader((DWORD*)shaderBuffer->GetBufferPointer(), &BasicShader);
1.4.3對著色器中的變量進(jìn)行賦值
1.3.4節(jié)說到著色器的全局變量在應(yīng)用程序中賦值而在著色器程序中使用,這是應(yīng)用程序和著色器通信的關(guān)鍵所在,這里就具體說明賦值過程。
著色器中的全局變量在編譯后都被放在一個(gè)叫常量表的結(jié)構(gòu)中,我們可以使用ID3DXConstantTable接口對其進(jìn)行訪問,參照1.4.1中編譯著色器函數(shù)D3DXCompileShaderFromFile的最后一個(gè)參數(shù),該參數(shù)即返回了指向常量表的指針。
對一個(gè)著色器中變量進(jìn)行賦值的步驟如下:
1. 通過變量名稱得到指向著色器變量的句柄;
還記得在BasicHLSL.x著色器文件中我們聲明的兩個(gè)全局變量嗎:
matrix WVPMatrix;
vector color;
我們在應(yīng)用程序中相應(yīng)的聲明兩個(gè)句柄:
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ColorHandle = 0;
然后通過變量名得到分別得到對應(yīng)的兩個(gè)句柄:
WVPMatrixHandle = BasicConstTable->GetConstantByName(0, "WVPMatrix");
ColorHandle = BasicConstTable->GetConstantByName(0, "color");
2. 通過句柄對著色器變量進(jìn)行賦值;
我們可以先設(shè)置各變量為默認(rèn)值:
BasicConstTable->SetDefaults(g_pd3dDevice);
之后,可以使用ID3DXConstantTable::SetXXX函數(shù)對各個(gè)變量進(jìn)行賦值:
HRESULT SetXXX(
LPDIRECT3DDEVICE9 pDevice,
D3DXHANDLE hConstant,
XXX value
);
其中XXX代表變量類型,例如Matrix類型的變量就要使用SetMatrix函數(shù)賦值,而Vector類型的則要使用SetVector來賦值。
1.4.4把著色器設(shè)定到渲染管道中
這里我們使用SetVertexShader方法把頂點(diǎn)著色器設(shè)定到渲染管道中:
g_pd3dDevice->SetVertexShader(BasicShader);
1.4.5整個(gè)渲染過程如下
在渲染過程中,我們設(shè)定頂點(diǎn)的變換坐標(biāo)和顏色值,渲染代碼如下:
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(153,153,153), 1.0f, 0 );
//開始渲染
g_pd3dDevice->BeginScene();
//得到世界矩陣、觀察矩陣和投影矩陣
D3DXMATRIX matWorld, matView, matProj;
g_pd3dDevice->GetTransform(D3DTS_WORLD, &matWorld);
g_pd3dDevice->GetTransform(D3DTS_VIEW, &matView);
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &matProj);
D3DXMATRIX matWVP = matWorld * matView * matProj;
//通過句柄對著色器中的WVPMatrix變量進(jìn)行賦值
BasicConstTable->SetMatrix(g_pd3dDevice, WVPMatrixHandle, &matWVP);
D3DXVECTOR4 color(1.0f, 1.0f, 0.0f, 1.0f);
//通過句柄對著色器中的color變量進(jìn)行賦值,這里我們賦值為黃色
BasicConstTable->SetVector(g_pd3dDevice, ColorHandle, &color);
//把頂點(diǎn)著色器設(shè)定到渲染管道中
g_pd3dDevice->SetVertexShader(BasicShader);
//繪制模型子集
Teapot->DrawSubset(0);
//渲染完畢
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
編譯運(yùn)行程序,運(yùn)行效果如圖1.2所示,這里我們將頂點(diǎn)顏色設(shè)置為黃色,如果讀者在渲染過程中不斷變換對著色器變量color的賦值,你將會(huì)得到一個(gè)色彩不斷變幻的D3D茶壺。
D3DXVECTOR4 color(1.0f, 1.0f, 0.0f, 1.0f); //讀者可以嘗試改變顏色值
BasicConstTable->SetVector(g_pd3dDevice, ColorHandle, &color);
圖1.2 著色器效果
2.頂點(diǎn)著色器
頂點(diǎn)著色器(vertex shader)是一個(gè)在顯卡的GPU上執(zhí)行的程序,它替換了固定功能管道(fixed function pipeline)中的變換(transformation)和光照(lighting)階段(這不是百分之百的正確,因?yàn)轫旤c(diǎn)著色器可以被Direct3D運(yùn)行時(shí)(Direct3D runtime)以軟件模擬,如果硬件不支持頂點(diǎn)著色器的話)。圖2.1說明了管線中頂點(diǎn)著色器替換的部件。
圖2.1
由于頂點(diǎn)著色器是我們(在HLSL中)寫的一個(gè)自定義程序,因此我們在圖形效果方面獲得了極大的自由性。我們不再受限于Direct3D的固定光照算法。此外,應(yīng)用程序操縱頂點(diǎn)位置的能力也有了多樣性,例如:布料仿真,粒子系統(tǒng)的點(diǎn)大小操縱,還有頂點(diǎn)混合/變形。此外,我們的頂點(diǎn)數(shù)據(jù)結(jié)構(gòu)更自由了,并且可以在可編程管線中包含比在固定功能管線中多的多的數(shù)據(jù)。
正如作者所在群的公告所說,“拍照不在于你對相機(jī)使用的熟練程度,而是在于你對藝術(shù)的把握。”之前的介紹使讀者對著色器的編寫和使用都有了一定的了解,下面我們將把重心從介紹如何使用著色器轉(zhuǎn)到如何實(shí)現(xiàn)更高級的渲染效果上來。
2.1可編程數(shù)據(jù)流模型
DirectX 8.0引入了數(shù)據(jù)流的概念,可以這樣理解數(shù)據(jù)流(圖2.2):
圖2.2
· 一個(gè)頂點(diǎn)由n個(gè)數(shù)據(jù)流組成。
· 一個(gè)數(shù)據(jù)流由m個(gè)元素組成。
· 一個(gè)元素是[位置、顏色、法向、紋理坐標(biāo)]。
程序中使用IDirect3DDevice9::SetStreamSource方法把一個(gè)頂點(diǎn)緩存綁定到一個(gè)設(shè)備數(shù)據(jù)流。
2.2頂點(diǎn)聲明
該小節(jié)對頂點(diǎn)聲明的描述絕大多數(shù)都取自翁云兵的《著色器和效果》,該文對頂點(diǎn)聲明的描述是我所見到最詳盡最透徹的,這里向作者表示敬意:)
到現(xiàn)在為止,我們已經(jīng)使用自由頂點(diǎn)格式(flexible vertex format,F(xiàn)VF)來描述頂點(diǎn)結(jié)構(gòu)中的各分量。但是,在可編程管線中,我們的頂點(diǎn)數(shù)據(jù)可以包含比用FVF所能表達(dá)的多的多的數(shù)據(jù)。因此,我們通常使用更具表達(dá)性的并且更強(qiáng)有力的頂點(diǎn)聲明(vertex declaration)。
注意:我們?nèi)匀豢梢栽诳删幊坦芫€中使用FVF——如果我們的頂點(diǎn)格式可以這樣描述。不管怎樣,這只是為了方便,因?yàn)镕VF會(huì)在內(nèi)部被轉(zhuǎn)換為一個(gè)頂點(diǎn)聲明。
2.2.1 描述頂點(diǎn)聲明
我們將一個(gè)頂點(diǎn)聲明描述為一個(gè)D3DVERTEXELEMENT9結(jié)構(gòu)的數(shù)組。D3DVERTEXELEMENT9數(shù)組中的每個(gè)元素描述了一個(gè)頂點(diǎn)的分量。所以,如果你的頂點(diǎn)結(jié)構(gòu)有三個(gè)分量(例如:位置、法線、顏色),那么其相應(yīng)的頂點(diǎn)聲明將會(huì)被一個(gè)含3個(gè)元素的D3DVERTEXELEMENT9結(jié)構(gòu)數(shù)組描述。
D3DVERTEXELEMENT9結(jié)構(gòu)定義如下:
typedef struct _D3DVERTEXELEMENT9 {
BYTE Stream;
BYTE Offset;
BYTE Type;
BYTE Method;
BYTE Usage;
BYTE UsageIndex;
} D3DVERTEXELEMENT9;
² Stream——指定關(guān)聯(lián)到頂點(diǎn)分量的流;
² Offset——偏移,按字節(jié),相對于頂點(diǎn)結(jié)構(gòu)成員的頂點(diǎn)分量的開始。例如,如果頂點(diǎn)結(jié)構(gòu)是:
struct Vertex
{
D3DXVECTOR3 pos;
D3DXVECTOR3 normal;
};
……pos分量的偏移是0,因?yàn)樗堑谝粋€(gè)分量;normal分量的偏移是12,因?yàn)閟izeof(pos) == 12。換句話說,normal分量以Vertex的第12個(gè)字節(jié)為開始。
² Type——指定數(shù)據(jù)類型。它可以是D3DDECLTYPE枚舉類型的任意成員;完整列表請參見文檔。常用類型如下:
D3DDECLTYPE_FLOAT1——浮點(diǎn)數(shù)值
D3DDECLTYPE_FLOAT2——2D浮點(diǎn)向量
D3DDECLTYPE_FLOAT3——3D浮點(diǎn)向量
D3DDECLTYPE_FLOAT4——4D浮點(diǎn)向量
D3DDECLTYPE_D3DCOLOR—D3DCOLOR類型,它擴(kuò)展為RGBA浮點(diǎn)顏色向量(r, g, b, a),其每一分量都是歸一化到區(qū)間[0, 1]了的。
² Method——指定網(wǎng)格化方法。我們認(rèn)為這個(gè)參數(shù)是高級的,因此我們使用默認(rèn)值,標(biāo)識(shí)為D3DDECLMETHOD_DEFAULT。
² Usage——指定已計(jì)劃的對頂點(diǎn)分量的使用。例如,它是否準(zhǔn)備用于一個(gè)位置向量、法線向量、紋理坐標(biāo)等,有效的用途標(biāo)識(shí)符(usage identifier)是D3DDECLUSAGE枚舉類型的:
typedef enum _D3DDECLUSAGE {
D3DDECLUSAGE_POSITION = 0, // Position.
D3DDECLUSAGE_BLENDWEIGHTS = 1, // Blending weights.
D3DDECLUSAGE_BLENDINDICES = 2, // Blending indices.
D3DDECLUSAGE_NORMAL = 3, // Normal vector.
D3DDECLUSAGE_PSIZE = 4, // Vertex point size.
D3DDECLUSAGE_TEXCOORD = 5, // Texture coordinates.
D3DDECLUSAGE_TANGENT = 6, // Tangent vector.
D3DDECLUSAGE_BINORMAL = 7, // Binormal vector.
D3DDECLUSAGE_TESSFACTOR = 8, // Tessellation factor.
D3DDECLUSAGE_POSITIONT = 9, // Transformed position.
D3DDECLUSAGE_COLOR = 10, // Color.
D3DDECLUSAGE_FOG = 11, // Fog blend value.
D3DDECLUSAGE_DEPTH = 12, // Depth value.
D3DDECLUSAGE_SAMPLE = 13 // Sampler data.
} D3DDECLUSAGE;
其中,D3DDECLUSAGE_PSIZE類型用于指定一個(gè)頂點(diǎn)的點(diǎn)的大小。它用于點(diǎn)精靈,因此我們可以基于每個(gè)頂點(diǎn)控制其大小。一個(gè)D3DDECLUSAGE_POSITION成員的頂點(diǎn)聲明意味著這個(gè)頂點(diǎn)已經(jīng)被變換,它通知圖形卡不要把這個(gè)頂點(diǎn)送到頂點(diǎn)處理階段(變形和光照)。
² UsageIndex——用于標(biāo)識(shí)多個(gè)相同用途的頂點(diǎn)分量。這個(gè)用途索引是位于區(qū)間[0, 15]間的一個(gè)整數(shù)。例如,假設(shè)我們有三個(gè)用途為D3DDECLUSAGE_NORMAL的頂點(diǎn)分量。我們可以為第一個(gè)指定用途索引為0,為第二個(gè)指定用途索引為1,并且為第三個(gè)指定用途索引為2。按這種方式,我們可以通過其用途索引標(biāo)識(shí)每個(gè)特定的法線。
例:假設(shè)我們想要描述的頂點(diǎn)格式由兩個(gè)數(shù)據(jù)流組成,第一個(gè)數(shù)據(jù)流包含位置、法線、紋理坐標(biāo)3個(gè)分量,第二個(gè)數(shù)據(jù)流包含位置和紋理坐標(biāo)2個(gè)分量,頂點(diǎn)聲明可以指定如下:
D3DVERTEXELEMENT9 decl[] =
{
//第一個(gè)數(shù)據(jù)流,包含分量位置、法線、紋理坐標(biāo)
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,D3DDECLUSAGE_
POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 0 },
{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
TEXCOORD, 0 },
//第一個(gè)數(shù)據(jù)流,包含分量位置、紋理坐標(biāo)
{ 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
POSITION, 1 },
{ 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 1 },
D3DDECL_END()
};
D3DDECL_END宏用于初始化D3DVERTEXELEMENT9數(shù)組的最后一個(gè)頂點(diǎn)元素。
2.2.2創(chuàng)建頂點(diǎn)聲明
CreateVertexDeclaration函數(shù)用于創(chuàng)建頂點(diǎn)聲明,decl為指向上一小節(jié)定義的D3DVERTEXELEMENT9數(shù)組的指針,函數(shù)返回IDirect3DVertexDeclaration9指針g_Decl;
IDirect3DVertexDeclaration9 *g_Decl = NULL;
g_pd3dDevice->CreateVertexDeclaration(decl ,&g_Decl);
2.2.3設(shè)置頂點(diǎn)聲明
g_pd3dDevice->SetVertexDeclaration(g_Decl);
至此,可編程數(shù)據(jù)流模型、頂點(diǎn)聲明介紹完畢,在下面的例子中讀者將會(huì)有更連貫的理解。
2.3用頂點(diǎn)著色器實(shí)現(xiàn)漸變動(dòng)畫
2.3.1漸變動(dòng)畫(Morphing)
Morphing漸變是20世紀(jì)90年代出現(xiàn)的一種革命性的計(jì)算機(jī)圖形技術(shù),該技術(shù)使得動(dòng)畫序列平滑且易于處理,即使在低檔配置的計(jì)算機(jī)系統(tǒng)上也能正常運(yùn)行。
漸變是指隨時(shí)間的變化把一個(gè)形狀改變?yōu)榱硪粋€(gè)形狀。對我們而言,這些形狀就是Mesh網(wǎng)格模型。漸變網(wǎng)格模型的處理就是以時(shí)間軸為基準(zhǔn),逐漸地改變網(wǎng)格模型頂點(diǎn)的坐標(biāo),從一個(gè)網(wǎng)格模型的形狀漸變到另外一個(gè)。請看圖2.3:
圖2.3
我們在程序中使用兩個(gè)網(wǎng)格模型——源網(wǎng)格模型和目標(biāo)網(wǎng)格模型,設(shè)源網(wǎng)格模型中頂點(diǎn)1的坐標(biāo)為A(Ax,Ay,Az),目標(biāo)網(wǎng)格模型中對應(yīng)頂點(diǎn)1的坐標(biāo)為B(Bx,By,Bz),要計(jì)算漸變過程中時(shí)間點(diǎn)t所對應(yīng)的頂點(diǎn)1的坐標(biāo)C(Cx,Cy,Cz),我們使用如下方法:
T為源網(wǎng)格模型到目標(biāo)網(wǎng)格模型漸變所花費(fèi)的全部時(shí)間,得到時(shí)間點(diǎn)t占整個(gè)過程T的比例為:
S = t / T
那么頂點(diǎn)1在t時(shí)刻對應(yīng)的坐標(biāo)C為:
C = A * (1-S)+ B * S
這樣,在渲染過程中我們根據(jù)時(shí)間不斷調(diào)整S的值,就得到了從源網(wǎng)格模型(形狀一)到目標(biāo)網(wǎng)格模型(形狀二)的平滑過渡。
接下來將在程序里使用頂點(diǎn)著色器實(shí)現(xiàn)我們的漸變動(dòng)畫。
2.3.2漸變動(dòng)畫中的頂點(diǎn)聲明
程序中,我們設(shè)定一個(gè)頂點(diǎn)對應(yīng)兩個(gè)數(shù)據(jù)流,這兩個(gè)數(shù)據(jù)流分別包含了源網(wǎng)格模型的數(shù)據(jù)和目標(biāo)網(wǎng)格模型的數(shù)據(jù)。渲染過程中,我們在著色器里根據(jù)兩個(gè)數(shù)據(jù)流中的頂點(diǎn)數(shù)據(jù)以及時(shí)間值確定最終的頂點(diǎn)信息。
個(gè)數(shù)據(jù)流包含分量如下:
源網(wǎng)格模型數(shù)據(jù)流:頂點(diǎn)位置、頂點(diǎn)法線、紋理坐標(biāo);
目標(biāo)網(wǎng)格模型數(shù)據(jù)流:頂點(diǎn)位置、頂點(diǎn)法線;
注意目標(biāo)網(wǎng)格模型數(shù)據(jù)流沒有包含紋理坐標(biāo),因?yàn)榧y理對于兩個(gè)網(wǎng)格模型都是一樣的,所以僅使用源網(wǎng)格模型的紋理就可以了。
頂點(diǎn)聲明指定如下:
D3DVERTEXELEMENT9 decl[] =
{
//源網(wǎng)格模型數(shù)據(jù)流,包含分量位置、法線、紋理坐標(biāo)
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,D3DDECLUSAGE_
POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 0 },
{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
TEXCOORD, 0 },
//目標(biāo)網(wǎng)格模型數(shù)據(jù)流,包含分量位置、紋理坐標(biāo)
{ 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
POSITION, 1 },
{ 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 1 },
D3DDECL_END()
};
2.3.3漸變動(dòng)畫中的頂點(diǎn)著色器
下面給出頂點(diǎn)著色器源碼,代碼存儲(chǔ)于vs.txt中,該頂點(diǎn)著色器根據(jù)源網(wǎng)格模型數(shù)據(jù)流和目標(biāo)網(wǎng)格模型數(shù)據(jù)流中的信息以及時(shí)間標(biāo)尺值計(jì)算出頂點(diǎn)最終位置信息,并對頂點(diǎn)做了坐標(biāo)變換和光照處理。代碼中給出了詳細(xì)的注釋,幫助讀者理解。
//全局變量
//世界矩陣、觀察矩陣、投影矩陣的合矩陣,用于頂點(diǎn)的坐標(biāo)變換
matrix WVPMatrix;
//光照方向
vector LightDirection;
//存儲(chǔ)2.3.1小節(jié)提到的公式S = t / T中的時(shí)間標(biāo)尺S值
//注意到Scalar是一個(gè)vector類型,我們在Scalar.x中存儲(chǔ)了S值,Scalar.y中存儲(chǔ)的則是(1-S)值
vector Scalar;
//輸入
struct VS_INPUT
{
//對應(yīng)源網(wǎng)格模型數(shù)據(jù)流中的頂點(diǎn)分量:位置、法線、紋理坐標(biāo)
vector position : POSITION;
vector normal : NORMAL;
float2 uvCoords : TEXCOORD;
//對應(yīng)目標(biāo)網(wǎng)格模型數(shù)據(jù)流中的頂點(diǎn)分量:位置、法線
vector position1 : POSITION1;
vector normal1 : NORMAL1;
};
//輸出
struct VS_OUTPUT
{
vector position : POSITION;
vector diffuse : COLOR;
float2 uvCoords : TEXCOORD;
};
//入口函數(shù)
VS_OUTPUT Main(VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
//頂點(diǎn)最終位置output.position取決于源網(wǎng)格模型數(shù)據(jù)流中位置信息input.position和目標(biāo)網(wǎng)格模型數(shù)據(jù)流中位置信息input.position1以及時(shí)間標(biāo)尺Scalar的值
//對應(yīng)2.3.1小節(jié)中的公式C = A * (1-S)+ B * S
output.position = input.position*Scalar.x + input.position1*Scalar.y;
//頂點(diǎn)坐標(biāo)變換操作
output.position = mul(output.position, WVPMatrix);
//計(jì)算頂點(diǎn)最終法線值
vector normal = input.normal*Scalar.x + input.normal1*Scalar.y;
//逆光方向與法線的點(diǎn)積,獲得漫射色彩
output.diffuse = dot((-LightDirection), normal);
//存儲(chǔ)紋理坐標(biāo)
output.uvCoords = input.uvCoords;
return output;
}
以上是本例用到的頂點(diǎn)著色器,在接下來的應(yīng)用程序中,我們將給三個(gè)著色器全局變量賦值:
² WVPMatrix;
世界矩陣、觀察矩陣、投影矩陣的合矩陣,用于頂點(diǎn)的坐標(biāo)變換;
² LightDirection
光照方向;
² Scalar
存儲(chǔ)2.3.1小節(jié)提到的公式S = t / T中的時(shí)間標(biāo)尺S值;
注意到Scalar是一個(gè)vector類型,我們在Scalar.x中存儲(chǔ)了S值,Scalar.y中存儲(chǔ)的則是(1-S)值;
2.3.4應(yīng)用程序
我們在應(yīng)用程序中執(zhí)行以下操作:
· 加載兩個(gè)兩個(gè)Mesh模型:源網(wǎng)格模型,目標(biāo)網(wǎng)格模型;
· 創(chuàng)建、設(shè)置頂點(diǎn)聲明;
· 創(chuàng)建、設(shè)置頂點(diǎn)著色器;
· 為著色器全局賦值;
· 把兩個(gè)Mesh模型數(shù)據(jù)分別綁定到兩個(gè)數(shù)據(jù)流中;
· 渲染Mesh模型;
下面是應(yīng)用程序代碼:
…
/*********************聲明變量*****************/
//兩個(gè)指向LPD3DXMESH的指針,分別用于存儲(chǔ)源網(wǎng)格模型和目標(biāo)網(wǎng)格模型;
LPD3DXMESH g_SourceMesh;
LPD3DXMESH g_TargetMesh;
//頂點(diǎn)聲明指針
IDirect3DVertexDeclaration9 *g_Decl = NULL;
//頂點(diǎn)著色器
IDirect3DVertexShader9 *g_VS = NULL;
//常量表
ID3DXConstantTable* ConstTable = NULL;
//常量句柄
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ScalarHandle = 0;
D3DXHANDLE LightDirHandle = 0;
…
/***************程序初始化*****************/
//加載源、目標(biāo)網(wǎng)格模型
Load_Meshes();
//頂點(diǎn)聲明
D3DVERTEXELEMENT9 MorphMeshDecl[] =
{
//1st stream is for source mesh - position, normal, texcoord
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 },
{ 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
//2nd stream is for target mesh - position, normal
{ 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 1 },
{ 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 1 },
D3DDECL_END()
};
//創(chuàng)建頂點(diǎn)著色器
ID3DXBuffer* shader = NULL;
ID3DXBuffer* errorBuffer = NULL;
D3DXCompileShaderFromFile("vs.txt",
0,
0,
"Main", // entry point function name
"vs_1_1",
D3DXSHADER_DEBUG,
&shader,
&errorBuffer,
&ConstTable);
if(errorBuffer)
{
::MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0);
ReleaseCOM(errorBuffer);
}
//創(chuàng)建頂點(diǎn)著色器
g_pd3dDevice->CreateVertexShader((DWORD*)shader->GetBufferPointer(), &g_VS);
//創(chuàng)建頂點(diǎn)聲明
g_pd3dDevice->CreateVertexDeclaration(MorphMeshDecl ,&g_Decl);
//得到各常量句柄
WVPMatrixHandle = ConstTable->GetConstantByName(0, "WVPMatrix");
ScalarHandle = ConstTable->GetConstantByName(0, "Scalar");
LightDirHandle = ConstTable->GetConstantByName(0, "LightDirection");
//為著色器全局變量LightDirection賦值
ConstTable->SetVector(g_pd3dDevice, LightDirHandle, &D3DXVECTOR4(0.0f, -1.0f, 0.0f, 0.0f));
//設(shè)置各著色器變量為默認(rèn)值
ConstTable->SetDefaults(g_pd3dDevice);
…
/*******************渲染*******************/
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB(153,153,153), 1.0f, 0 );
g_pd3dDevice->BeginScene();
//為著色器全局變量WVPMatrix賦值
D3DXMATRIX matWorld, matView, matProj;
g_pd3dDevice->GetTransform(D3DTS_WORLD, &matWorld);
g_pd3dDevice->GetTransform(D3DTS_VIEW, &matView);
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &matProj);
D3DXMATRIX matWVP;
matWVP = matWorld * matView * matProj;
ConstTable->SetMatrix(g_pd3dDevice, WVPMatrixHandle, &matWVP);
//為著色器全局變量Scalar賦值,注意程序中獲取時(shí)間標(biāo)尺值Scalar的方法
float DolphinTimeFactor = (float)(timeGetTime() % 501) / 250.0f;
float Scalar =
(DolphinTimeFactor<=1.0f)?DolphinTimeFactor:(2.0f-DolphinTimeFactor);
ConstTable->SetVector(g_pd3dDevice,ScalarHandle,&D3DXVECTOR4(1.0f-Scalar, Scalar, 0.0f, 0.0f));
//設(shè)置頂點(diǎn)著色器和頂點(diǎn)聲明
g_pd3dDevice->SetVertexShader(g_VS);
g_pd3dDevice->SetVertexDeclaration(g_Decl);
//綁定目標(biāo)網(wǎng)格模型的定點(diǎn)緩存到第二個(gè)數(shù)據(jù)流中
IDirect3DVertexBuffer9 *pVB = NULL;
g_TargetMesh->GetVertexBuffer(&pVB);
g_pd3dDevice->SetStreamSource(1, pVB, 0,
D3DXGetFVFVertexSize(g_TargetMesh->GetFVF()));
ReleaseCOM(pVB);
//綁定源網(wǎng)格模型的頂點(diǎn)緩存到第一個(gè)數(shù)據(jù)流中
g_SourceMesh->GetVertexBuffer(&pVB);
g_pd3dDevice->SetStreamSource(0, pVB, 0,
D3DXGetFVFVertexSize(g_TargetMesh->GetFVF()));
ReleaseCOM(pVB);
//繪制Mesh網(wǎng)格模型
DrawMesh(g_SourceMesh, g_pMeshTextures0, g_VS, g_Decl);
g_pd3dDevice->EndScene();
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
…
2.3.5對應(yīng)用程序的一點(diǎn)說明
程序中我們使用SetStreamSource方法把源網(wǎng)格模型和目標(biāo)網(wǎng)格模型中的頂點(diǎn)緩存分別綁定到兩個(gè)設(shè)備數(shù)據(jù)流,但是Direct3D對數(shù)據(jù)流中的數(shù)據(jù)的真正引用只有在調(diào)用諸如DrawPrimitive、DrawIndexedPrimitive之類的繪制方法時(shí)才發(fā)生,因此在繪制Mesh網(wǎng)格模型時(shí)我們不能再使用傳統(tǒng)的DrawSubmit方法,而是使用了DrawIndexedPrimitive,下面就如何調(diào)用DrawIndexedPrimitive繪制Mesh模型進(jìn)行說明,該部分內(nèi)容和HLSL著色器關(guān)系不大,在這里列出僅僅是為了大家理解程序的完整性,讀者完全可以跳過本節(jié)不看。
使用DrawIndexedPrimitive繪制Mesh模型的步驟如下:
1. 加載網(wǎng)格模型后使用OptimizeInPlace方法對Mesh進(jìn)行優(yōu)化;
2. 一旦優(yōu)化了網(wǎng)格模型,你就可以查詢ID3DXMesh對象,得到一個(gè)D3DXATTRIBUTERANGE數(shù)據(jù)類型的數(shù)組,我們稱之為屬性列表,該數(shù)據(jù)類型被定義如下:
typedef struct_D3DXATTRIBUTERANGE{
DWORD AttribId; //子集編號(hào)
DWORD FaceStart; //這兩個(gè)變量用于圈定本子集中的多邊形
DWORD FaceCount;
DWORD VertexStart; //這兩個(gè)變量用于圈定本子集中的頂點(diǎn)
DWORD VertexCount;
} D3DXATTRIBUTERANGE;
我們屬性列表中的每一項(xiàng)都代表一個(gè)被優(yōu)化后Mesh的一個(gè)子集,D3DXATTRIBUTERANGE結(jié)構(gòu)的各字段描述了該子集的信息。
1. 得到屬性數(shù)據(jù)后,我們就調(diào)用DrawIndexedPrimitive方法可以精美地渲染子集了。
下面是繪制Mesh模型的程序代碼:
在Load_Meshes()函數(shù)的最后,我們使用OptimizeInPlace方法對源網(wǎng)格模型和目標(biāo)網(wǎng)格模型進(jìn)行優(yōu)化,其他加載材質(zhì)和紋理的操作和之前一樣,相信大家能夠理解:
…
//優(yōu)化源網(wǎng)格模型
g_SourceMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL);
…
//優(yōu)化目標(biāo)網(wǎng)格模型
g_TargetMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL);
…
在Draw_Mesh()函數(shù)中,渲染模型,注意程序是如何配合屬性表調(diào)用DrawIndexedPrimitive方法進(jìn)行繪制的:
…
//分別得到指向Mesh模型頂點(diǎn)緩存區(qū)和索引緩存區(qū)的指針
IDirect3DVertexBuffer9 *pVB = NULL;
IDirect3DIndexBuffer9 *pIB = NULL;
pMesh->GetVertexBuffer(&pVB);
pMesh->GetIndexBuffer(&pIB);
//得到Mesh模型的屬性列表
DWORD NumAttributes;
D3DXATTRIBUTERANGE *pAttributes = NULL;
pMesh->GetAttributeTable(NULL, &NumAttributes);
pAttributes = new D3DXATTRIBUTERANGE[NumAttributes];
pMesh->GetAttributeTable(pAttributes, &NumAttributes);
//設(shè)置頂點(diǎn)著色器和頂點(diǎn)聲明
g_pd3dDevice->SetVertexShader(pShader);
g_pd3dDevice->SetVertexDeclaration(pDecl);
//設(shè)置數(shù)據(jù)流
g_pd3dDevice->SetStreamSource(0, pVB, 0, D3DXGetFVFVertexSize(pMesh->GetFVF()));
g_pd3dDevice->SetIndices(pIB);
//遍歷屬性列表并配合其中的信息調(diào)用DrawIndexPrimitive繪制各個(gè)子集
for(DWORD i=0;i<NumAttributes;i++)
{
if(pAttributes[i].FaceCount)
{
//Get material number
DWORD MatNum = pAttributes[i].AttribId;
//Set texture
g_pd3dDevice->SetTexture(0, pTextures[MatNum]);
//Draw the mesh subset
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0,
pAttributes[i].VertexStart,
pAttributes[i].VertexCount,
pAttributes[i].FaceStart * 3,
pAttributes[i].FaceCount);
}
}
//Free resources
ReleaseCOM(pVB);
ReleaseCOM(pIB);
delete [] pAttributes;
…
編譯運(yùn)行程序,效果如圖2.4所示,你將看到屏幕上白色的海豚上下翻騰,同時(shí)感受到頂點(diǎn)著色器為渲染效果所帶來的巨大改善。
圖2.4
3.像素著色器
像素著色器是在對每個(gè)像素進(jìn)行光柵化處理期間在圖形卡的GPU上執(zhí)行的程序。(不像頂點(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)、混雜陰影化技巧(sophisticated shadowing technique)。
像素著色器的編寫、使用和頂點(diǎn)著色器大同小異,有了之前的基礎(chǔ),不用太過于詳細(xì)的介紹相信讀者也能理解,下面使用像素著色器實(shí)現(xiàn)多紋理化。
3.1多紋理化
簡單的說,多紋理化就是使用多個(gè)紋理貼圖混合后進(jìn)行渲染,如圖3.1,渲染過程中,從紋理1和紋理2中分別采樣,得到的顏色值依據(jù)一定規(guī)則進(jìn)行組合得到紋理3,這就是多紋理化。
圖3.1
3.2多紋理效果的像素著色器
下面是像素著色器的代碼,該代碼存儲(chǔ)于ps.txt中,該像素著色器根據(jù)輸入的兩套紋理坐標(biāo)對對應(yīng)的紋理貼圖進(jìn)行采樣,根據(jù)一定比例Scalar混合后輸出像素顏色。
//全局變量
//存儲(chǔ)顏色混合的比例值s,其中
//Scalar.x = s
//Scalar.y = 1-s
vector Scalar;
//紋理
texture Tex0;
texture Tex1;
//紋理采樣器
sampler Samp0 =
sampler_state
{
Texture = <Tex0>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
sampler Samp1 =
sampler_state
{
Texture = <Tex1>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
//輸入兩套紋理坐標(biāo)
struct PS_INPUT
{
float2 uvCoords0 : TEXCOORD0;
float2 uvCoords1 : TEXCOORD1;
};
//輸出像素顏色
struct PS_OUTPUT
{
float4 Color : COLOR0;
};
//入口函數(shù)
PS_OUTPUT PS_Main(PS_INPUT input)
{
PS_OUTPUT output = (PS_OUTPUT)0;
//分別對兩個(gè)紋理進(jìn)行采樣按照比例混合后輸出顏色值
output.Color = tex2D(Samp0, input.uvCoords0)*Scalar.x + tex2D(Samp1, input.uvCoords1)*Scalar.y;
return output;
}
整個(gè)程序很容易理解,程序中涉及到著色器的紋理和采樣,是我們第一次接觸的內(nèi)容,下面給于說明。
3.2.1HLSL采樣器和紋理
和vector、matrix一樣,采樣器sample和紋理texture也是HLSL語言的一種類型,HLSL著色器使用采樣器對指定紋理進(jìn)行采樣,得到采樣后的顏色值以供處理。
它們的用法如下:
//聲明一個(gè)紋理變量
texture g_texture;
//定義采樣器
sampler g_samp =
sampler_state
{
//關(guān)聯(lián)到紋理
Texture = <g_texture>;
//設(shè)置采樣器狀態(tài)
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
//調(diào)用HLSL內(nèi)置函數(shù)tex2D取得顏色值,參數(shù)一為采樣器,參數(shù)二為紋理坐標(biāo)
vector Color = tex2D(g_samp, uvCoords);
更多HLSL采樣器和紋理的內(nèi)容請參見DirectX文檔。
以上是本例用到的像素著色器,在接下來的應(yīng)用程序中,我們將給三個(gè)著色器全局變量賦值:
² Scalar
存儲(chǔ)顏色混合的比例值s,其中Scalar.x = s, Scalar.y = 1-s;
² Samp0
第一層紋理采樣器;
² Samp1
第二層紋理采樣器;
像素著色器的輸入結(jié)構(gòu)中我們設(shè)定了一個(gè)頂點(diǎn)對應(yīng)兩套紋理坐標(biāo),讀者可以留意一下應(yīng)用程序中對應(yīng)的頂點(diǎn)格式的定義。
3.3應(yīng)用程序
程序中我們首先創(chuàng)建一個(gè)四邊形,然后使用像素著色器進(jìn)行紋理混合后對其進(jìn)行渲染。下面是應(yīng)用程序代碼:
…
/*********************頂點(diǎn)格式定義*****************/
struct CUSTOMVERTEX
{
//定點(diǎn)位置坐標(biāo)
float x,y,z;
//兩套紋理坐標(biāo);
float tu0, tv0;
float tu1, tv1;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX2)
…
/*********************聲明變量*****************/
//頂點(diǎn)著色器
LPDIRECT3DPIXELSHADER9 pixelShader = 0;
//常量表
ID3DXConstantTable* pixelConstTable = 0;
//常量句柄
D3DXHANDLE ScalarHandle = 0;
D3DXHANDLE Samp0Handle = 0;
D3DXHANDLE Samp1Handle = 0;
//常量描述結(jié)構(gòu)
D3DXCONSTANT_DESC Samp0Desc;
D3DXCONSTANT_DESC Samp1Desc;
//四邊形頂點(diǎn)緩存
LPDIRECT3DVERTEXBUFFER9 quadVB = NULL;
//兩個(gè)紋理
LPDIRECT3DTEXTURE9 quadTexture0 = NULL;
LPDIRECT3DTEXTURE9 quadTexture1 = NULL;
…
/********************初始化應(yīng)用程序*****************/
//創(chuàng)建四邊形頂點(diǎn)模型
CUSTOMVERTEX quad[] =
// x y z tu0 tv0 tu1 tv1
{{-3.0f, -3.0f, 10.0f, 0.0f, 1.0f, 0.0f, 1.0f},
{ -3.0f, 3.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 3.0f, -3.0f, 10.0f, 1.0f, 1.0f, 1.0f, 1.0f},
{ 3.0f, 3.0f, 10.0f, 1.0f, 0.0f, 1.0f, 0.0f}};
//創(chuàng)建頂點(diǎn)緩存
void *ptr = NULL;
g_pd3dDevice->CreateVertexBuffer(sizeof(quad),
D3DUSAGE_WRITEONLY,
0,
D3DPOOL_MANAGED,
&quadVB,
NULL);
quadVB->Lock(0, 0, (void**)&ptr, 0);
memcpy((void*)ptr, (void*)quad, sizeof(quad));
quadVB->Unlock();
//創(chuàng)建紋理
D3DXCreateTextureFromFile(g_pd3dDevice, "porpcart.jpg", &quadTexture0);
D3DXCreateTextureFromFile(g_pd3dDevice, "luoqi.jpg", &quadTexture1);
//檢測系統(tǒng)是否支持像素著色器
D3DCAPS9 caps;
g_pd3dDevice->GetDeviceCaps(&caps);
if(caps.PixelShaderVersion < D3DPS_VERSION(1, 1))
{
MessageBox(0, "NotSupport Pixel Shader - FAILED", 0, 0);
exit(0);
}
//創(chuàng)建像素著色器
ID3DXBuffer* codeBuffer = 0;
ID3DXBuffer* errorBuffer = 0;
HRESULT hr = D3DXCompileShaderFromFile("ps.txt",
0,
0,
"PS_Main", // entry point function name
"ps_1_1",
D3DXSHADER_DEBUG,
&codeBuffer,
&errorBuffer,
&pixelConstTable);
// output any error messages
if(errorBuffer)
{
MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0);
ReleaseCOM(errorBuffer);
}
if(FAILED(hr))
{
MessageBox(0, "D3DXCompileShaderFromFile() - FAILED", 0, 0);
return false;
}
hr = g_pd3dDevice->CreatePixelShader((DWORD*)codeBuffer->GetBufferPointer(), &pixelShader);
if(FAILED(hr))
{
MessageBox(0, "CreatePixelShader - FAILED", 0, 0);
return false;
}
ReleaseCOM(codeBuffer);
ReleaseCOM(errorBuffer);
//得到各常量句柄
ScalarHandle = pixelConstTable->GetConstantByName(0, "Scalar");
Samp0Handle = pixelConstTable->GetConstantByName(0, "Samp0");
Samp1Handle = pixelConstTable->GetConstantByName(0, "Samp1");
//得到對著色器變量Samp0、Samp0的描述
UINT count;
pixelConstTable->GetConstantDesc(Samp0Handle, & Samp0Desc, &count);
pixelConstTable->GetConstantDesc(Samp1Handle, & Samp1Desc, &count);
//設(shè)定各著色器變量為初始值
pixelConstTable->SetDefaults(g_pd3dDevice);
…
/********************渲染*****************/
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(153,153,153), 1.0f, 0 );
g_pd3dDevice->BeginScene();
//為著色器全局變量Scalar賦值
D3DXVECTOR4 scalar(0.5f, 0.5f, 0.0f, 1.0f);
pixelConstTable->SetVector(g_pd3dDevice, ScalarHandle, &scalar);
//設(shè)置像素著色器
g_pd3dDevice->SetPixelShader(pixelShader);
//設(shè)置定點(diǎn)格式、綁定數(shù)據(jù)流
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->SetStreamSource(0, quadVB, 0, sizeof(CUSTOMVERTEX));
//設(shè)置第一、二層紋理
g_pd3dDevice->SetTexture(Samp0Desc.RegisterIndex, quadTexture0);
g_pd3dDevice->SetTexture(Samp1Desc.RegisterIndex, quadTexture1);
//繪制圖形
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
…
程序中像素著色器的使用和之前頂點(diǎn)著色器的使用無二,只是設(shè)置著色器中紋理采樣器變量Samp0、Samp1和設(shè)定著色器其他變量稍有不同:
1. 首先通過變量名稱得到變量句柄:
Tex0Handle = pixelConstTable->GetConstantByName(0, " Samp0");
Tex1Handle = pixelConstTable->GetConstantByName(0, " Samp1");
2. 然后通過句柄得到對變量的描述:
UINT count;
pixelConstTable->GetConstantDesc(Samp0Handle, & Samp0Desc, &count);
pixelConstTable->GetConstantDesc(Samp1Handle, & Samp1Desc, &count);
3.最后通過SetTexture配合所得到的描述信息設(shè)置紋理:
g_pd3dDevice->SetTexture(Samp0Desc.RegisterIndex, quadTexture0);
g_pd3dDevice->SetTexture(Samp1Desc.RegisterIndex, quadTexture1);
編譯運(yùn)行程序,運(yùn)行效果如圖3.2,這里我們將顏色混合比例設(shè)置為0.5,如果讀者在渲染過程中不斷變換對著色器變量Scalar的賦值,你將會(huì)得到一個(gè)混合度不斷變換的多紋理效果。
D3DXVECTOR4 scalar(0.5f, 0.5f, 0.0f, 1.0f); //讀者可以嘗試改變混合采用的比例值
pixelConstTable->SetVector(g_pd3dDevice, ScalarHandle, &scalar);
紋理一
紋理二
混合后紋理三
圖3.2
4.HLSL Effect(效果框架)
進(jìn)行到這里,讀者可能會(huì)覺得使用著色器多少有些繁瑣,Effect(效果框架)被提出以解決這些問題。作為一種方法,Effect簡化了使用著色器的操作;作為一個(gè)框架,Effect把頂點(diǎn)著色器和像素著色器有機(jī)地組織了起來。
4.1Effect代碼結(jié)構(gòu)
一個(gè)Effect效果代碼的結(jié)構(gòu)如下:
//effect
technique T0
{
pass P0
{
...
}
}
technique T1
{
pass P0
{
...
}
pass P1
{
...
}
}
...
technique Tn
{
pass P0
{
...
}
}
首先理解三個(gè)術(shù)語effect(效果)、technique(技術(shù))、pass(過程),所幸這三個(gè)術(shù)語從字面意思上就能得到很好的詮釋。
要實(shí)現(xiàn)一種效果effect,可以使用多種技術(shù)technique,而每種技術(shù)中可能使用多個(gè)過程pass進(jìn)行渲染,這樣就構(gòu)成了上述effect包含多個(gè)technique,technique又包含多個(gè)pass的代碼結(jié)構(gòu)。
理解了代碼結(jié)構(gòu),effect知識(shí)就已經(jīng)掌握了大半,下面我們直接使用一個(gè)程序?qū)嵗龑ffect進(jìn)行介紹。
4.2用Effect實(shí)現(xiàn)多紋理化效果
前面我們介紹了一個(gè)使用像素著色器實(shí)現(xiàn)的多紋理化,這里用Effect框架重新給于實(shí)現(xiàn),讀者可以比較兩者之間的異同,體會(huì)Effect框架給我們帶來了哪些方面的改善。
4.2.1著色器
下面是著色器代碼,該代碼存儲(chǔ)于Effect.txt中,代碼中包含了一個(gè)頂點(diǎn)著色器和一個(gè)像素著色器和一個(gè)Effect效果框架。
//---------------------------------------------
// 頂點(diǎn)著色器
//---------------------------------------------
matrix WVPMatrix;
struct VS_INPUT
{
vector position : POSITION;
float2 uvCoords0 : TEXCOORD0;
float2 uvCoords1 : TEXCOORD1;
};
struct VS_OUTPUT
{
vector position : POSITION;
float2 uvCoords0 : TEXCOORD0;
float2 uvCoords1 : TEXCOORD1;
};
VS_OUTPUT VS_Main(VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.position = mul(input.position, WVPMatrix);
output.uvCoords0 = input.uvCoords0;
output.uvCoords1 = input.uvCoords1;
return output;
}
//---------------------------------------------
// 像素著色器
//---------------------------------------------
vector Scalar;
texture Tex0;
texture Tex1;
sampler Samp0 =
sampler_state
{
Texture = <Tex0>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
sampler Samp1 =
sampler_state
{
Texture = <Tex1>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
struct PS_INPUT
{
float2 uvCoords0 : TEXCOORD0;
float2 uvCoords1 : TEXCOORD1;
};
struct PS_OUTPUT
{
float4 Color : COLOR0;
};
PS_OUTPUT PS_Main(PS_INPUT input)
{
PS_OUTPUT output = (PS_OUTPUT)0;
output.Color = tex2D(Samp0, input.uvCoords0)*Scalar.x + tex2D(Samp1, input.uvCoords1)*Scalar.y;
return output;
}
//---------------------------------------------
// 效果框架
//---------------------------------------------
technique T0
{
pass P0
{
vertexShader = compile vs_1_1 VS_Main();
pixelShader = compile ps_1_1 PS_Main();
}
}
注意程序中是如何使用效果框架將頂點(diǎn)著色器和像素著色器組織起來的:
pass P0
{
//著色器類型 版本號(hào) 入口函數(shù)名稱
vertexShader = compile vs_1_1 VS_Main();
pixelShader = compile ps_1_1 PS_Main();
}
也可以直接將著色代碼寫在pass過程中,相關(guān)用法請讀者參看DirectX文檔:
pass P0
{
//這里書寫著色器代碼
…
}
有了之前的基礎(chǔ),著色器代碼讀者應(yīng)該很容易理解,下面具體介紹如何在應(yīng)用程序中使用Effect。
4.2.2應(yīng)用程序
…
/*********************頂點(diǎn)格式定義*****************/
struct CUSTOMVERTEX
{
//定點(diǎn)位置坐標(biāo)
float x,y,z;
//兩套紋理坐標(biāo);
float tu0, tv0;
float tu1, tv1;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX2)
…
/*********************聲明變量*****************/
//Effect效果指針
ID3DXEffect *g_pEffect = 0;
//常量句柄
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ScalarHandle = 0;
D3DXHANDLE Tex0Handle = 0;
D3DXHANDLE Tex1Handle = 0;
D3DXHANDLE TechHandle = 0;
//四邊形頂點(diǎn)緩存
LPDIRECT3DVERTEXBUFFER9 quadVB = NULL;
//兩個(gè)紋理
LPDIRECT3DTEXTURE9 quadTexture0 = NULL;
LPDIRECT3DTEXTURE9 quadTexture1 = NULL;
…
/********************初始化應(yīng)用程序*****************/
//定義四邊頂點(diǎn)模型
CUSTOMVERTEX quad[] =
// x y z tu0 tv0 tu1 tv1
{{-3.0f, -3.0f, 10.0f, 0.0f, 1.0f, 0.0f, 1.0f},
{ -3.0f, 3.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{ 3.0f, -3.0f, 10.0f, 1.0f, 1.0f, 1.0f, 1.0f},
{ 3.0f, 3.0f, 10.0f, 1.0f, 0.0f, 1.0f, 0.0f}};
//設(shè)置頂點(diǎn)緩存
void *ptr = NULL;
g_pd3dDevice->CreateVertexBuffer(sizeof(quad),
D3DUSAGE_WRITEONLY,
0,
D3DPOOL_MANAGED,
&quadVB,
NULL);
quadVB->Lock(0, 0, (void**)&ptr, 0);
memcpy((void*)ptr, (void*)quad, sizeof(quad));
quadVB->Unlock();
//創(chuàng)建紋理
D3DXCreateTextureFromFile(g_pd3dDevice, "chopper.bmp", &quadTexture0);
D3DXCreateTextureFromFile(g_pd3dDevice, "Bleach.jpg", &quadTexture1);
//檢測像素著色器是否被支持
D3DCAPS9 caps;
g_pd3dDevice->GetDeviceCaps(&caps);
if(caps.PixelShaderVersion < D3DPS_VERSION(1, 1))
{
MessageBox(0, "NotSupport Pixel Shader - FAILED", 0, 0);
exit(0);
}
//創(chuàng)建Effect效果
ID3DXBuffer* errorBuffer = 0;
HRESULT hr = D3DXCreateEffectFromFile(g_pd3dDevice,
"Effect.txt",
0,
0,
D3DXSHADER_DEBUG,
0,
&g_pEffect,
&errorBuffer);
// output any error messages
if(errorBuffer)
{
MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0);
ReleaseCOM(errorBuffer);
exit(0);
}
if(FAILED(hr))
{
MessageBox(0, "D3DXCreateEffectFromFile() - FAILED", 0, 0);
return false;
}
//得到各常量句柄
WVPMatrixHandle = g_pEffect->GetParameterByName(0, "WVPMatrix");
ScalarHandle = g_pEffect->GetParameterByName(0, "Scalar");
Tex0Handle = g_pEffect->GetParameterByName(0, "Tex0");
Tex1Handle = g_pEffect->GetParameterByName(0, "Tex1");
//得到技術(shù)technique T0的句柄
TechHandle = g_pEffect->GetTechniqueByName("T0");
//設(shè)置紋理,注意這里設(shè)置紋理的方式比之前像素著色器簡便很多
g_pEffect->SetTexture(Tex0Handle, quadTexture0);
g_pEffect->SetTexture(Tex1Handle, quadTexture1);
…
/********************渲染*****************/
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(153,153,153), 1.0f, 0 );
g_pd3dDevice->BeginScene();
//為著色器變量WVPMatrix賦值
D3DXMATRIX matWorld, matView, matProj;
g_pd3dDevice->GetTransform(D3DTS_WORLD, &matWorld);
g_pd3dDevice->GetTransform(D3DTS_VIEW, &matView);
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &matProj);
D3DXMATRIX matWVP = matWorld * matView * matProj;
g_pEffect->SetMatrix(WVPMatrixHandle, &matWVP);
//為著色器全局變量Scalar賦值
D3DXVECTOR4 scalar(0.5f, 0.5f, 0.0f, 1.0f);
g_pEffect->SetVector(ScalarHandle, &scalar);
//設(shè)置定點(diǎn)格式、綁定數(shù)據(jù)流
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->SetStreamSource(0, quadVB, 0, sizeof(CUSTOMVERTEX));
//注意下面使用effect框架進(jìn)行渲染的方法
//設(shè)置要使用的技術(shù)
g_pEffect->SetTechnique(TechHandle);
//遍歷技術(shù)中包含的所有過程進(jìn)行多次渲染
UINT numPasses = 0;
g_pEffect->Begin(&numPasses, 0);
for(UINT i = 0; i<numPasses; ++i)
{
//開始過程
g_pEffect->BeginPass(i);
//繪制圖形
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
//結(jié)束過程
g_pEffect->EndPass();
}
//結(jié)束使用技術(shù)
g_pEffect->End();
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
…
以上是應(yīng)用程序中使用Effect框架的代碼,可以看到Effect在著色器加載、著色器變量賦值、頂點(diǎn)著色器和像素著色器配合使用等方面做出了簡化,這里只是個(gè)簡單的例子,當(dāng)讀者深入學(xué)習(xí)Effect的時(shí)候,會(huì)了解到更多Effect框架為著色器編程提供的方便。
編譯運(yùn)行程序,運(yùn)行效果如圖4.1所示,這和第三章使用像素著色器實(shí)現(xiàn)的多紋理化效果是一樣的。
紋理一
紋理二
混合后紋理三
圖4.1
本文來自CSDN博客,轉(zhuǎn)載請標(biāo)明出處:http://blog.csdn.net/chpdirect1984/archive/2007/12/02/1911622.aspx