前言算法
1.HLSL入門編程
1.1什麼是着色器windows
1.2什麼是HLSL數組
1.3怎麼寫HLSL着色器緩存
1.4怎麼用HLSL着色器數據結構
2.頂點着色器框架
2.1可編程數據流模型ide
2.2頂點聲明模塊化
2.3用頂點着色器實現漸變更畫函數
3.像素着色器
3.1多紋理化
3.2多紋理效果的像素着色器
3.3應用程序
4.HLSL Effect(效果框架)
4.1Effect代碼結構
4.2用Effect實現多紋理化效果
結語
參考資料
本教程針對HLSL(High Level Shading Language)初學者,從應用的角度對HLSL、頂點着色器、像素着色器和Effect效果框架進行了介紹,教程中去掉了對HLSL語法等一些細節內容的討論,力求幫助讀者儘量快地理解HLSL編程的概念,掌握HLSL編程的方法。
教程中部分闡述直接引用了其餘文檔,這是由於這些文檔表述之精要,已經達到了不能更改的地步,這裏表示感謝。
本文檔版權爲做者全部,非商業用途可無償使用,轉載請註明出處。
1.1什麼是着色器
DirectX使用管道技術(pipeline)進行圖形渲染,其構架以下:
圖1.1 Direct3D Graphics Pipeline
以前咱們使用管道的步驟以下:
1. 設定頂點、圖元、紋理等數據信息;
2. 設定管道狀態信息;
² 渲染狀態
經過SetRenderState方法設定渲染狀態;
另外,使用如下方法設置變換、材質和光照:
SetTransform
SetMaterial
SetLight
LightEnable
² 取樣器狀態
經過SetSamplerState方法設定取樣器狀態;
² 紋理層狀態
經過SetTextureStageState設定紋理層狀態;
3. 渲染;
這部分交由D3D管道按照以前的設定自行完成,這部分操做是D3D預先固定的,因此這種管道技術被稱爲固定功能管道(fixed function pipeline);
固定功能管道給咱們編程提供了必定的靈活性,可是仍有不少效果難以經過這種方式實現,好比:
1. 在渲染過程當中,咱們要求y座標值大於10的頂點要被繪製到座標值(0,0,0)的地方,在以前的固定功能管道中,頂點被繪製的位置是在第1步即被設定好的,不可能在渲染過程當中進行改變,因此是不可行的;
2. 謀頂點在紋理貼圖1上映射爲點A,在紋理貼圖2上映射爲點B,咱們要求該頂點顏色由A、B共同決定,即:
定點顏色 = A點色彩值*0.7 + B點色彩值*0.3
這在固定管道編程中也是不可行的。
以上兩個問題均可以由可編程管道(pragrammable pipeline)來解決。
可編程管線容許用戶自定義一段能夠在GPU上執行的程序,代替固定管道技術中的Vertex Processing和Pixel Processing階段(參照圖1.1),從而在使咱們在編程中達到更大的靈活性。其中替換Vertex Processing的部分叫作Vertex Shader(頂點着色器),替換Pixel Proccessing的部分叫作Pixel Shader(像素着色器),這就是咱們所說的着色器Shader。
1.2什麼是HLSL
Direct8.x中,着色器是經過低級着色彙編語言來編寫的,這樣的程序更像是彙編式的指令集合,因爲其效率低、可讀性差、版本限制等缺點,迫切要求出現一門更高級的着色語言。到了Direct3D9,HLSL(High Level Shading Language,高級渲染語言)應運而生了。
HLSL的語法很是相似於C和C++,學習起來是很方便的。
1.3怎麼寫HLSL着色器
咱們能夠直接把HLSL着色器代碼做爲一長串字符串編寫進咱們的應用程序源文件中,可是,更加方便和模塊化的方法是把着色器的代碼從應用程序代碼中分離出來。所以,咱們將着色器代碼單獨保存爲文本格式,而後在應用程序中使用特定函數將其加載進來。
下面是一個完整的HLSL着色器程序代碼,咱們把它保存在BasicHLSL.txt中。該着色器完成頂點的世界變換、觀察變換和投影變幻,並將頂點顏色設定爲指定的顏色。
//
// 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全局變量
代碼中聲明瞭兩個全局變量:
matrix WVPMatrix;
vector color;
變量WVPMatrix是一個矩陣類型,它包含了世界、觀察、投影的合矩陣,用於對頂點進行座標變換;
變量color是一個向量類型,它用於設定頂點顏色;
代碼中並無對全局變量進行初始化,這是由於咱們對全局變量的初始化過程將在應用程序中進行,全局變量在應用程序中賦值而在着色器程序中使用,這是應用程序和着色器通訊的關鍵所在。具體賦值過程將在後續部分講述。
1.3.2輸入輸出
² 輸入輸出結構
程序中定義了兩個輸入輸出結構VS_INPUT和VS_OUTPUT
struct VS_INPUT
{
vector position : POSITION;
};
struct VS_OUTPUT
{
vector position : POSITION;
vector color : COLOR;
};
自定義的結構能夠採用任意名稱,結構不過是一種組織數據的方式,並非強制的,你也能夠不使用,而將本程序的輸入改成:
vector position : POSITION;
² 標誌符
用於輸入輸出的變量採用用一種特殊的聲明方式:
Type VariableName : Semantic
這個特殊的冒號語法表示一個語義,冒號後面的標誌符用來指定變量的用途,如
vector position : POSITION;
其中,POSITION標誌符代表該變量表示頂點位置,另外還有諸如COLOR、NORMAL等不少表示其餘意義的標誌符。
本節所說的輸入輸出實際上是指着色器代碼和編譯器、GPU之間的通訊,和應用程序是無關的,因此這些變量不須要在應用程序中進行賦值,標誌符告訴編譯器各個輸入輸出變量的用途(頂點位置、法線、顏色等),這是着色器代碼和編譯器、GPU之間通訊的關鍵。
1.3.3入口函數
程序中還定義了一個函數SetColor:
OUTPUT SetColor(INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
output.position = mul(input.position, WVPMatrix);
output.color = color;
return output;
}
1. 該函數以input和output類型做爲輸入輸出;
2. 使全局變量WVPMatrix和input.position相乘,以完成頂點的世界、觀察、投影變換,並把結果賦值到output.position;
output.position = mul(input.position, WVPMatrix);
3. 將全局變量color的值賦給output.color;
output.color = color;
4. 在同一個着色器代碼文件中,能夠有多個用戶自定義函數,所以在應用程序中須要指定一個入口函數,至關於windows程序的WinMain函數,本程序只包含SetColor一個函數並且它將被作爲入口函數使用。
1.3.4總結
至此,一個HLSL着色器編寫完畢,渲染過程當中,當一個頂點被送到着色器時:
1. 全局變量WVPMatrix、color將在應用程序中被賦值;
2. 入口函數SetColor被調用編譯器根據標誌符將頂點信息填充到VS_INPUT中的各個字段;
3. SetColor函數中,首先定義一個VS_OUTPUT信息,以後根據WVPMatrix和color變量完成頂點的座標變換和顏色設定操做,最後函數返回VS_OUTPUT結構;
4. 編譯器將會再次根據標誌符把返回的VS_OUTPUT結構中的各字段映射爲頂點相應的信息。
5. 頂點被送往下一個流程接受進一步處理。
上述過程當中,全局變量在應用程序中賦值而在着色器程序中使用,這是應用程序和着色器通訊的關鍵所在;標誌符告訴編譯器各個輸入輸出變量的用途(頂點位置、法線、顏色等),這是着色器代碼和編譯器、GPU之間通訊的關鍵。我的認爲這是着色器中最爲精義的地方:)
1.4怎麼用HLSL着色器
應用程序中對HLSL着色器的使用分爲如下步驟:
1. 加載(稱爲編譯更爲穩當)着色器代碼;
2. 建立(頂點/像素)着色器;
3. 對着色器中的變量進行賦值,完成應用程序和着色器之間的通訊。
4. 把着色器設定到渲染管道中;
本例使用的着色器是一個頂點着色器,所以咱們將經過頂點着色器的使用來說解着色器的使用過程,像素着色器的使用過程與此大同小異,兩者之間僅有些微差異。
1.4.1聲明全局變量
IDirect3DVertexShader9* BasicShader = 0; //頂點着色器指針
ID3DXConstantTable* BasicConstTable = 0; //常量表指針
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ColorHandle = 0;
ID3DXMesh* Teapot = 0; //指向程序中D3D茶壺模型的指針
1.4.2編譯着色器
經過D3DXCompileShaderFromFile函數從應用程序外部的文本文件BasicHLSL.txt中編譯一個着色器:
//編譯後的着色器代碼將被放在一個buffer中,能夠經過ID3DXBuffer接口對其進行訪問,以後的着色器將從這裏建立
ID3DXBuffer* shaderBuffer = 0;
//用於接受錯誤信息
ID3DXBuffer* errorBuffer = 0;
//編譯着色器代碼
D3DXCompileShaderFromFile("BasicHLSL.txt", //着色器代碼文件名
0,
0,
"SetColor", //入口函數名稱
"vs_1_1", //頂點着色器版本號
D3DXSHADER_DEBUG,// Debug模式編譯
&shaderBuffer, //指向編譯後的着色器代碼的指針
&errorBuffer,
&BasicConstTable); //常量表指針
1.4.3建立着色器
應用程序經過CreateVertexShader建立一個頂點着色器,注意使用了上一步獲得的shaderBuffer:
g_pd3dDevice->CreateVertexShader((DWORD*)shaderBuffer->GetBufferPointer(), &BasicShader);
1.4.3對着色器中的變量進行賦值
1.3.4節說到着色器的全局變量在應用程序中賦值而在着色器程序中使用,這是應用程序和着色器通訊的關鍵所在,這裏就具體說明賦值過程。
着色器中的全局變量在編譯後都被放在一個叫常量表的結構中,咱們可使用ID3DXConstantTable接口對其進行訪問,參照1.4.1中編譯着色器函數D3DXCompileShaderFromFile的最後一個參數,該參數即返回了指向常量表的指針。
對一個着色器中變量進行賦值的步驟以下:
1. 經過變量名稱獲得指向着色器變量的句柄;
還記得在BasicHLSL.x着色器文件中咱們聲明的兩個全局變量嗎:
matrix WVPMatrix;
vector color;
咱們在應用程序中相應的聲明兩個句柄:
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ColorHandle = 0;
而後經過變量名獲得分別獲得對應的兩個句柄:
WVPMatrixHandle = BasicConstTable->GetConstantByName(0, "WVPMatrix");
ColorHandle = BasicConstTable->GetConstantByName(0, "color");
2. 經過句柄對着色器變量進行賦值;
咱們能夠先設置各變量爲默認值:
BasicConstTable->SetDefaults(g_pd3dDevice);
以後,可使用ID3DXConstantTable::SetXXX函數對各個變量進行賦值:
HRESULT SetXXX(
LPDIRECT3DDEVICE9 pDevice,
D3DXHANDLE hConstant,
XXX value
);
其中XXX表明變量類型,例如Matrix類型的變量就要使用SetMatrix函數賦值,而Vector類型的則要使用SetVector來賦值。
1.4.4把着色器設定到渲染管道中
這裏咱們使用SetVertexShader方法把頂點着色器設定到渲染管道中:
g_pd3dDevice->SetVertexShader(BasicShader);
1.4.5整個渲染過程以下
在渲染過程當中,咱們設定頂點的變換座標和顏色值,渲染代碼以下:
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變量進行賦值
BasicConstTable->SetMatrix(g_pd3dDevice, WVPMatrixHandle, &matWVP);
D3DXVECTOR4 color(1.0f, 1.0f, 0.0f, 1.0f);
//經過句柄對着色器中的color變量進行賦值,這裏咱們賦值爲黃色
BasicConstTable->SetVector(g_pd3dDevice, ColorHandle, &color);
//把頂點着色器設定到渲染管道中
g_pd3dDevice->SetVertexShader(BasicShader);
//繪製模型子集
Teapot->DrawSubset(0);
//渲染完畢
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
編譯運行程序,運行效果如圖1.2所示,這裏咱們將頂點顏色設置爲黃色,若是讀者在渲染過程當中不斷變換對着色器變量color的賦值,你將會獲得一個色彩不斷變幻的D3D茶壺。
D3DXVECTOR4 color(1.0f, 1.0f, 0.0f, 1.0f); //讀者能夠嘗試改變顏色值
BasicConstTable->SetVector(g_pd3dDevice, ColorHandle, &color);
圖1.2 着色器效果
頂點着色器(vertex shader)是一個在顯卡的GPU上執行的程序,它替換了固定功能管道(fixed function pipeline)中的變換(transformation)和光照(lighting)階段(這不是百分之百的正確,由於頂點着色器能夠被Direct3D運行時(Direct3D runtime)以軟件模擬,若是硬件不支持頂點着色器的話)。圖2.1說明了管線中頂點着色器替換的部件。
圖2.1
因爲頂點着色器是咱們(在HLSL中)寫的一個自定義程序,所以咱們在圖形效果方面得到了極大的自由性。咱們再也不受限於Direct3D的固定光照算法。此外,應用程序操縱頂點位置的能力也有了多樣性,例如:布料仿真,粒子系統的點大小操縱,還有頂點混合/變形。此外,咱們的頂點數據結構更自由了,而且能夠在可編程管線中包含比在固定功能管線中多的多的數據。
正如做者所在羣的公告所說,「拍照不在於你對相機使用的熟練程度,而是在於你對藝術的把握。」以前的介紹使讀者對着色器的編寫和使用都有了必定的瞭解,下面咱們將把重心從介紹如何使用着色器轉到如何實現更高級的渲染效果上來。
2.1可編程數據流模型
DirectX 8.0引入了數據流的概念,能夠這樣理解數據流(圖2.2):
圖2.2
程序中使用IDirect3DDevice9::SetStreamSource方法把一個頂點緩存綁定到一個設備數據流。
2.2頂點聲明
該小節對頂點聲明的描述絕大多數都取自翁雲兵的《着色器和效果》,該文對頂點聲明的描述是我所見到最詳盡最透徹的,這裏向做者表示敬意:)
到如今爲止,咱們已經使用自由頂點格式(flexible vertex format,FVF)來描述頂點結構中的各份量。可是,在可編程管線中,咱們的頂點數據能夠包含比用FVF所能表達的多的多的數據。所以,咱們一般使用更具表達性的而且更強有力的頂點聲明(vertex declaration)。
注意:咱們仍然能夠在可編程管線中使用FVF——若是咱們的頂點格式能夠這樣描述。無論怎樣,這只是爲了方便,由於FVF會在內部被轉換爲一個頂點聲明。
2.2.1 描述頂點聲明
咱們將一個頂點聲明描述爲一個D3DVERTEXELEMENT9結構的數組。D3DVERTEXELEMENT9數組中的每一個元素描述了一個頂點的份量。因此,若是你的頂點結構有三個份量(例如:位置、法線、顏色),那麼其相應的頂點聲明將會被一個含3個元素的D3DVERTEXELEMENT9結構數組描述。
D3DVERTEXELEMENT9結構定義以下:
typedef struct _D3DVERTEXELEMENT9 {
BYTE Stream;
BYTE Offset;
BYTE Type;
BYTE Method;
BYTE Usage;
BYTE UsageIndex;
} D3DVERTEXELEMENT9;
² Stream——指定關聯到頂點份量的流;
² Offset——偏移,按字節,相對於頂點結構成員的頂點份量的開始。例如,若是頂點結構是:
struct Vertex
{
D3DXVECTOR3 pos;
D3DXVECTOR3 normal;
};
……pos份量的偏移是0,由於它是第一個份量;normal份量的偏移是12,由於sizeof(pos) == 12。換句話說,normal份量以Vertex的第12個字節爲開始。
² Type——指定數據類型。它能夠是D3DDECLTYPE枚舉類型的任意成員;完整列表請參見文檔。經常使用類型以下:
D3DDECLTYPE_FLOAT1——浮點數值
D3DDECLTYPE_FLOAT2——2D浮點向量
D3DDECLTYPE_FLOAT3——3D浮點向量
D3DDECLTYPE_FLOAT4——4D浮點向量
D3DDECLTYPE_D3DCOLOR—D3DCOLOR類型,它擴展爲RGBA浮點顏色向量(r, g, b, a),其每一份量都是歸一化到區間[0, 1]了的。
² Method——指定網格化方法。咱們認爲這個參數是高級的,所以咱們使用默認值,標識爲D3DDECLMETHOD_DEFAULT。
² Usage——指定已計劃的對頂點份量的使用。例如,它是否準備用於一個位置向量、法線向量、紋理座標等,有效的用途標識符(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類型用於指定一個頂點的點的大小。它用於點精靈,所以咱們能夠基於每一個頂點控制其大小。一個D3DDECLUSAGE_POSITION成員的頂點聲明意味着這個頂點已經被變換,它通知圖形卡不要把這個頂點送到頂點處理階段(變形和光照)。
² UsageIndex——用於標識多個相同用途的頂點份量。這個用途索引是位於區間[0, 15]間的一個整數。例如,假設咱們有三個用途爲D3DDECLUSAGE_NORMAL的頂點份量。咱們能夠爲第一個指定用途索引爲0,爲第二個指定用途索引爲1,而且爲第三個指定用途索引爲2。按這種方式,咱們能夠經過其用途索引標識每一個特定的法線。
例:假設咱們想要描述的頂點格式由兩個數據流組成,第一個數據流包含位置、法線、紋理座標3個份量,第二個數據流包含位置和紋理座標2個份量,頂點聲明能夠指定以下:
D3DVERTEXELEMENT9 decl[] =
{
//第一個數據流,包含份量位置、法線、紋理座標
{ 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 },
//第一個數據流,包含份量位置、紋理座標
{ 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
POSITION, 1 },
{ 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 1 },
D3DDECL_END()
};
D3DDECL_END宏用於初始化D3DVERTEXELEMENT9數組的最後一個頂點元素。
2.2.2建立頂點聲明
CreateVertexDeclaration函數用於建立頂點聲明,decl爲指向上一小節定義的D3DVERTEXELEMENT9數組的指針,函數返回IDirect3DVertexDeclaration9指針g_Decl;
IDirect3DVertexDeclaration9 *g_Decl = NULL;
g_pd3dDevice->CreateVertexDeclaration(decl ,&g_Decl);
2.2.3設置頂點聲明
g_pd3dDevice->SetVertexDeclaration(g_Decl);
至此,可編程數據流模型、頂點聲明介紹完畢,在下面的例子中讀者將會有更連貫的理解。
2.3用頂點着色器實現漸變更畫
2.3.1漸變更畫(Morphing)
Morphing漸變是20世紀90年代出現的一種革命性的計算機圖形技術,該技術使得動畫序列平滑且易於處理,即便在低檔配置的計算機系統上也能正常運行。
漸變是指隨時間的變化把一個形狀改變爲另外一個形狀。對咱們而言,這些形狀就是Mesh網格模型。漸變網格模型的處理就是以時間軸爲基準,逐漸地改變網格模型頂點的座標,從一個網格模型的形狀漸變到另一個。請看圖2.3:
圖2.3
咱們在程序中使用兩個網格模型——源網格模型和目標網格模型,設源網格模型中頂點1的座標爲A(Ax,Ay,Az),目標網格模型中對應頂點1的座標爲B(Bx,By,Bz),要計算漸變過程當中時間點t所對應的頂點1的座標C(Cx,Cy,Cz),咱們使用以下方法:
T爲源網格模型到目標網格模型漸變所花費的所有時間,獲得時間點t佔整個過程T的比例爲:
S = t / T
那麼頂點1在t時刻對應的座標C爲:
C = A * (1-S)+ B * S
這樣,在渲染過程當中咱們根據時間不斷調整S的值,就獲得了從源網格模型(形狀一)到目標網格模型(形狀二)的平滑過渡。
接下來將在程序裏使用頂點着色器實現咱們的漸變更畫。
2.3.2漸變更畫中的頂點聲明
程序中,咱們設定一個頂點對應兩個數據流,這兩個數據流分別包含了源網格模型的數據和目標網格模型的數據。渲染過程當中,咱們在着色器里根據兩個數據流中的頂點數據以及時間值肯定最終的頂點信息。
個數據流包含份量以下:
源網格模型數據流:頂點位置、頂點法線、紋理座標;
目標網格模型數據流:頂點位置、頂點法線;
注意目標網格模型數據流沒有包含紋理座標,由於紋理對於兩個網格模型都是同樣的,因此僅使用源網格模型的紋理就能夠了。
頂點聲明指定以下:
D3DVERTEXELEMENT9 decl[] =
{
//源網格模型數據流,包含份量位置、法線、紋理座標
{ 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 },
//目標網格模型數據流,包含份量位置、紋理座標
{ 1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
POSITION, 1 },
{ 1, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_
NORMAL, 1 },
D3DDECL_END()
};
2.3.3漸變更畫中的頂點着色器
下面給出頂點着色器源碼,代碼存儲於vs.txt中,該頂點着色器根據源網格模型數據流和目標網格模型數據流中的信息以及時間標尺值計算出頂點最終位置信息,並對頂點作了座標變換和光照處理。代碼中給出了詳細的註釋,幫助讀者理解。
//全局變量
//世界矩陣、觀察矩陣、投影矩陣的合矩陣,用於頂點的座標變換
matrix WVPMatrix;
//光照方向
vector LightDirection;
//存儲2.3.1小節提到的公式S = t / T中的時間標尺S值
//注意到Scalar是一個vector類型,咱們在Scalar.x中存儲了S值,Scalar.y中存儲的則是(1-S)值
vector Scalar;
//輸入
struct VS_INPUT
{
//對應源網格模型數據流中的頂點份量:位置、法線、紋理座標
vector position : POSITION;
vector normal : NORMAL;
float2 uvCoords : TEXCOORD;
//對應目標網格模型數據流中的頂點份量:位置、法線
vector position1 : POSITION1;
vector normal1 : NORMAL1;
};
//輸出
struct VS_OUTPUT
{
vector position : POSITION;
vector diffuse : COLOR;
float2 uvCoords : TEXCOORD;
};
//入口函數
VS_OUTPUT Main(VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT)0;
//頂點最終位置output.position取決於源網格模型數據流中位置信息input.position和目標網格模型數據流中位置信息input.position1以及時間標尺Scalar的值
//對應2.3.1小節中的公式C = A * (1-S)+ B * S
output.position = input.position*Scalar.x + input.position1*Scalar.y;
//頂點座標變換操做
output.position = mul(output.position, WVPMatrix);
//計算頂點最終法線值
vector normal = input.normal*Scalar.x + input.normal1*Scalar.y;
//逆光方向與法線的點積,得到漫射色彩
output.diffuse = dot((-LightDirection), normal);
//存儲紋理座標
output.uvCoords = input.uvCoords;
return output;
}
以上是本例用到的頂點着色器,在接下來的應用程序中,咱們將給三個着色器全局變量賦值:
² WVPMatrix;
世界矩陣、觀察矩陣、投影矩陣的合矩陣,用於頂點的座標變換;
² LightDirection
光照方向;
² Scalar
存儲2.3.1小節提到的公式S = t / T中的時間標尺S值;
注意到Scalar是一個vector類型,咱們在Scalar.x中存儲了S值,Scalar.y中存儲的則是(1-S)值;
2.3.4應用程序
咱們在應用程序中執行如下操做:
下面是應用程序代碼:
…
/*********************聲明變量*****************/
//兩個指向LPD3DXMESH的指針,分別用於存儲源網格模型和目標網格模型;
LPD3DXMESH g_SourceMesh;
LPD3DXMESH g_TargetMesh;
//頂點聲明指針
IDirect3DVertexDeclaration9 *g_Decl = NULL;
//頂點着色器
IDirect3DVertexShader9 *g_VS = NULL;
//常量表
ID3DXConstantTable* ConstTable = NULL;
//常量句柄
D3DXHANDLE WVPMatrixHandle = 0;
D3DXHANDLE ScalarHandle = 0;
D3DXHANDLE LightDirHandle = 0;
…
/***************程序初始化*****************/
//加載源、目標網格模型
Load_Meshes();
//頂點聲明
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()
};
//建立頂點着色器
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);
}
//建立頂點着色器
g_pd3dDevice->CreateVertexShader((DWORD*)shader->GetBufferPointer(), &g_VS);
//建立頂點聲明
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));
//設置各着色器變量爲默認值
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賦值,注意程序中獲取時間標尺值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));
//設置頂點着色器和頂點聲明
g_pd3dDevice->SetVertexShader(g_VS);
g_pd3dDevice->SetVertexDeclaration(g_Decl);
//綁定目標網格模型的定點緩存到第二個數據流中
IDirect3DVertexBuffer9 *pVB = NULL;
g_TargetMesh->GetVertexBuffer(&pVB);
g_pd3dDevice->SetStreamSource(1, pVB, 0,
D3DXGetFVFVertexSize(g_TargetMesh->GetFVF()));
ReleaseCOM(pVB);
//綁定源網格模型的頂點緩存到第一個數據流中
g_SourceMesh->GetVertexBuffer(&pVB);
g_pd3dDevice->SetStreamSource(0, pVB, 0,
D3DXGetFVFVertexSize(g_TargetMesh->GetFVF()));
ReleaseCOM(pVB);
//繪製Mesh網格模型
DrawMesh(g_SourceMesh, g_pMeshTextures0, g_VS, g_Decl);
g_pd3dDevice->EndScene();
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
…
2.3.5對應用程序的一點說明
程序中咱們使用SetStreamSource方法把源網格模型和目標網格模型中的頂點緩存分別綁定到兩個設備數據流,可是Direct3D對數據流中的數據的真正引用只有在調用諸如DrawPrimitive、DrawIndexedPrimitive之類的繪製方法時才發生,所以在繪製Mesh網格模型時咱們不能再使用傳統的DrawSubmit方法,而是使用了DrawIndexedPrimitive,下面就如何調用DrawIndexedPrimitive繪製Mesh模型進行說明,該部份內容和HLSL着色器關係不大,在這裏列出僅僅是爲了你們理解程序的完整性,讀者徹底能夠跳過本節不看。
使用DrawIndexedPrimitive繪製Mesh模型的步驟以下:
1. 加載網格模型後使用OptimizeInPlace方法對Mesh進行優化;
2. 一旦優化了網格模型,你就能夠查詢ID3DXMesh對象,獲得一個D3DXATTRIBUTERANGE數據類型的數組,咱們稱之爲屬性列表,該數據類型被定義以下:
typedef struct_D3DXATTRIBUTERANGE{
DWORD AttribId; //子集編號
DWORD FaceStart; //這兩個變量用於圈定本子集中的多邊形
DWORD FaceCount;
DWORD VertexStart; //這兩個變量用於圈定本子集中的頂點
DWORD VertexCount;
} D3DXATTRIBUTERANGE;
咱們屬性列表中的每一項都表明一個被優化後Mesh的一個子集,D3DXATTRIBUTERANGE結構的各字段描述了該子集的信息。
1. 獲得屬性數據後,咱們就調用DrawIndexedPrimitive方法能夠精美地渲染子集了。
下面是繪製Mesh模型的程序代碼:
在Load_Meshes()函數的最後,咱們使用OptimizeInPlace方法對源網格模型和目標網格模型進行優化,其餘加載材質和紋理的操做和以前同樣,相信你們可以理解:
…
//優化源網格模型
g_SourceMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL);
…
//優化目標網格模型
g_TargetMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL);
…
在Draw_Mesh()函數中,渲染模型,注意程序是如何配合屬性表調用DrawIndexedPrimitive方法進行繪製的:
…
//分別獲得指向Mesh模型頂點緩存區和索引緩存區的指針
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);
//設置頂點着色器和頂點聲明
g_pd3dDevice->SetVertexShader(pShader);
g_pd3dDevice->SetVertexDeclaration(pDecl);
//設置數據流
g_pd3dDevice->SetStreamSource(0, pVB, 0, D3DXGetFVFVertexSize(pMesh->GetFVF()));
g_pd3dDevice->SetIndices(pIB);
//遍歷屬性列表並配合其中的信息調用DrawIndexPrimitive繪製各個子集
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;
…
編譯運行程序,效果如圖2.4所示,你將看到屏幕上白色的海豚上下翻騰,同時感覺到頂點着色器爲渲染效果所帶來的巨大改善。
圖2.4
像素着色器是在對每一個像素進行光柵化處理期間在圖形卡的GPU上執行的程序。(不像頂點着色器,Direct3D不會以軟件模擬像素着色器的功能。)它實際上替換了固定功能管線的多紋理化階段(the multitexturing stage),並賦予咱們直接操縱單獨的像素和訪問每一個像素的紋理座標的能力。這種對像素和紋理座標的直接訪問使咱們能夠達成各類特效,例如:多紋理化(multitexturing)、每像素光照(per pixel lighting)、景深(depth of field)、雲狀物模擬(cloud simulation)、焰火模擬(fire simulation)、混雜陰影化技巧(sophisticated shadowing technique)。
像素着色器的編寫、使用和頂點着色器大同小異,有了以前的基礎,不用太過於詳細的介紹相信讀者也能理解,下面使用像素着色器實現多紋理化。
3.1多紋理化
簡單的說,多紋理化就是使用多個紋理貼圖混合後進行渲染,如圖3.1,渲染過程當中,從紋理1和紋理2中分別採樣,獲得的顏色值依據必定規則進行組合獲得紋理3,這就是多紋理化。
圖3.1
3.2多紋理效果的像素着色器
下面是像素着色器的代碼,該代碼存儲於ps.txt中,該像素着色器根據輸入的兩套紋理座標對對應的紋理貼圖進行採樣,根據必定比例Scalar混合後輸出像素顏色。
//全局變量
//存儲顏色混合的比例值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;
};
//輸入兩套紋理座標
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;
}
整個程序很容易理解,程序中涉及到着色器的紋理和採樣,是咱們第一次接觸的內容,下面給於說明。
3.2.1HLSL採樣器和紋理
和vector、matrix同樣,採樣器sample和紋理texture也是HLSL語言的一種類型,HLSL着色器使用採樣器對指定紋理進行採樣,獲得採樣後的顏色值以供處理。
它們的用法以下:
//聲明一個紋理變量
texture g_texture;
//定義採樣器
sampler g_samp =
sampler_state
{
//關聯到紋理
Texture = <g_texture>;
//設置採樣器狀態
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
//調用HLSL內置函數tex2D取得顏色值,參數一爲採樣器,參數二爲紋理座標
vector Color = tex2D(g_samp, uvCoords);
更多HLSL採樣器和紋理的內容請參見DirectX文檔。
以上是本例用到的像素着色器,在接下來的應用程序中,咱們將給三個着色器全局變量賦值:
² Scalar
存儲顏色混合的比例值s,其中Scalar.x = s, Scalar.y = 1-s;
² Samp0
第一層紋理採樣器;
² Samp1
第二層紋理採樣器;
像素着色器的輸入結構中咱們設定了一個頂點對應兩套紋理座標,讀者能夠留意一下應用程序中對應的頂點格式的定義。
3.3應用程序
程序中咱們首先建立一個四邊形,而後使用像素着色器進行紋理混合後對其進行渲染。下面是應用程序代碼:
…
/*********************頂點格式定義*****************/
struct CUSTOMVERTEX
{
//定點位置座標
float x,y,z;
//兩套紋理座標;
float tu0, tv0;
float tu1, tv1;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX2)
…
/*********************聲明變量*****************/
//頂點着色器
LPDIRECT3DPIXELSHADER9 pixelShader = 0;
//常量表
ID3DXConstantTable* pixelConstTable = 0;
//常量句柄
D3DXHANDLE ScalarHandle = 0;
D3DXHANDLE Samp0Handle = 0;
D3DXHANDLE Samp1Handle = 0;
//常量描述結構
D3DXCONSTANT_DESC Samp0Desc;
D3DXCONSTANT_DESC Samp1Desc;
//四邊形頂點緩存
LPDIRECT3DVERTEXBUFFER9 quadVB = NULL;
//兩個紋理
LPDIRECT3DTEXTURE9 quadTexture0 = NULL;
LPDIRECT3DTEXTURE9 quadTexture1 = NULL;
…
/********************初始化應用程序*****************/
//建立四邊形頂點模型
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}};
//建立頂點緩存
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();
//建立紋理
D3DXCreateTextureFromFile(g_pd3dDevice, "porpcart.jpg", &quadTexture0);
D3DXCreateTextureFromFile(g_pd3dDevice, "luoqi.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);
}
//建立像素着色器
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);
//設定各着色器變量爲初始值
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);
//設置像素着色器
g_pd3dDevice->SetPixelShader(pixelShader);
//設置定點格式、綁定數據流
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->SetStreamSource(0, quadVB, 0, sizeof(CUSTOMVERTEX));
//設置第1、二層紋理
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);
…
程序中像素着色器的使用和以前頂點着色器的使用無二,只是設置着色器中紋理採樣器變量Samp0、Samp1和設定着色器其餘變量稍有不一樣:
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配合所獲得的描述信息設置紋理:
g_pd3dDevice->SetTexture(Samp0Desc.RegisterIndex, quadTexture0);
g_pd3dDevice->SetTexture(Samp1Desc.RegisterIndex, quadTexture1);
編譯運行程序,運行效果如圖3.2,這裏咱們將顏色混合比例設置爲0.5,若是讀者在渲染過程當中不斷變換對着色器變量Scalar的賦值,你將會獲得一個混合度不斷變換的多紋理效果。
D3DXVECTOR4 scalar(0.5f, 0.5f, 0.0f, 1.0f); //讀者能夠嘗試改變混合採用的比例值
pixelConstTable->SetVector(g_pd3dDevice, ScalarHandle, &scalar);
紋理一
紋理二
混合後紋理三
圖3.2
進行到這裏,讀者可能會以爲使用着色器多少有些繁瑣,Effect(效果框架)被提出以解決這些問題。做爲一種方法,Effect簡化了使用着色器的操做;做爲一個框架,Effect把頂點着色器和像素着色器有機地組織了起來。
4.1Effect代碼結構
一個Effect效果代碼的結構以下:
//effect
technique T0
{
pass P0
{
...
}
}
technique T1
{
pass P0
{
...
}
pass P1
{
...
}
}
...
technique Tn
{
pass P0
{
...
}
}
首先理解三個術語effect(效果)、technique(技術)、pass(過程),所幸這三個術語從字面意思上就能獲得很好的詮釋。
要實現一種效果effect,可使用多種技術technique,而每種技術中可能使用多個過程pass進行渲染,這樣就構成了上述effect包含多個technique,technique又包含多個pass的代碼結構。
理解了代碼結構,effect知識就已經掌握了大半,下面咱們直接使用一個程序實例對effect進行介紹。
4.2用Effect實現多紋理化效果
前面咱們介紹了一個使用像素着色器實現的多紋理化,這裏用Effect框架從新給於實現,讀者能夠比較二者之間的異同,體會Effect框架給咱們帶來了哪些方面的改善。
4.2.1着色器
下面是着色器代碼,該代碼存儲於Effect.txt中,代碼中包含了一個頂點着色器和一個像素着色器和一個Effect效果框架。
//---------------------------------------------
// 頂點着色器
//---------------------------------------------
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();
}
}
注意程序中是如何使用效果框架將頂點着色器和像素着色器組織起來的:
pass P0
{
//着色器類型 版本號 入口函數名稱
vertexShader = compile vs_1_1 VS_Main();
pixelShader = compile ps_1_1 PS_Main();
}
也能夠直接將着色代碼寫在pass過程當中,相關用法請讀者參看DirectX文檔:
pass P0
{
//這裏書寫着色器代碼
…
}
有了以前的基礎,着色器代碼讀者應該很容易理解,下面具體介紹如何在應用程序中使用Effect。
4.2.2應用程序
…
/*********************頂點格式定義*****************/
struct CUSTOMVERTEX
{
//定點位置座標
float x,y,z;
//兩套紋理座標;
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;
//四邊形頂點緩存
LPDIRECT3DVERTEXBUFFER9 quadVB = NULL;
//兩個紋理
LPDIRECT3DTEXTURE9 quadTexture0 = NULL;
LPDIRECT3DTEXTURE9 quadTexture1 = NULL;
…
/********************初始化應用程序*****************/
//定義四邊頂點模型
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}};
//設置頂點緩存
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();
//建立紋理
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);
}
//建立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");
//獲得技術technique T0的句柄
TechHandle = g_pEffect->GetTechniqueByName("T0");
//設置紋理,注意這裏設置紋理的方式比以前像素着色器簡便不少
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);
//設置定點格式、綁定數據流
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->SetStreamSource(0, quadVB, 0, sizeof(CUSTOMVERTEX));
//注意下面使用effect框架進行渲染的方法
//設置要使用的技術
g_pEffect->SetTechnique(TechHandle);
//遍歷技術中包含的全部過程進行屢次渲染
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);
//結束過程
g_pEffect->EndPass();
}
//結束使用技術
g_pEffect->End();
g_pd3dDevice->EndScene();
g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
…
以上是應用程序中使用Effect框架的代碼,能夠看到Effect在着色器加載、着色器變量賦值、頂點着色器和像素着色器配合使用等方面作出了簡化,這裏只是個簡單的例子,當讀者深刻學習Effect的時候,會了解到更多Effect框架爲着色器編程提供的方便。
編譯運行程序,運行效果如圖4.1所示,這和第三章使用像素着色器實現的多紋理化效果是同樣的。
紋理一
紋理二
混合後紋理三
圖4.1