作者:trcj
目錄
前言
1.HLSL入門
1.1什么是著色器
1.2什么是HLSL
1.3怎么寫HLSL著色器
1.4怎么用HLSL著色器
2.頂點著色器
2.1可編程數據流模型
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編程的方法。
教程中部分闡述直接引用了其他文檔,這是因為這些文檔表述之精要,已經達到了不能更改的地步,這里表示感謝。
本文檔版權為作者所有,非商業用途可免費使用,轉載請注明出處。
作者也是HLSL的初學者,教程中難免紕漏之處,望大家指正。有任何意見請發信到taoboxiang2006@yahoo.com.cn或者留言到 http://blog.csdn.net/trcj1/進行討論。
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 著色器效果