第十六章
進階著色語言:High-Level Shading Language,HLSL
頂點著色器和像素著色器就是自行編寫的規模較小的定製程式(custom programs),這些定製程式取代固定功能流水線中的某一功能模組,並在圖形卡的GPU(Graphics Processing Unit,圖形處理器)中執行,通過這種功能替換,便在實現各種圖形效果時獲得了巨大的靈活性,也就是說,不再受制於那些預定義的"固定"運算
在Dirext 8.0x系列版本中,著色器程式是用底層的組合語言來編寫的
在Dirext 9.0x系列版本中,提供了一種專門用來編寫著色器程式的進階著色語言HLSL
HLSL和彙編相比的好處:
1 提高生產力,更快捷,更容易
2 增強可讀性,可讀性好,易於調式和維護
3 編譯器產生的彙編代碼比手工編寫的彙編代碼更高效
4 藉助HLSL編譯器,可將著色器代碼編譯為任何可用的著色器版本
5 學習HLSL語言大大縮短學習曲線
如果圖形卡不支援頂點著色器和像素著色器,需要切換REF裝置
頂點著色器可用軟體頂點運算(software vertex processing)方式來類比,即在建立裝置時,將裝置行為標記設定為D3DCREATE_SOFTWARE_VERTEXPROCESSING
HLSL著色器程式的編製
HLSL著色器程式可以用一個長字串的形式出現在應用程式的源檔案中,但更方便也更模組化的做法是將著色器代碼與應用程式代碼分離,基於上述考慮,可在記事本中編寫著色器代碼並將其儲存為常規的ASCII文字檔,接下來級可以使用函數D3DXCompileShaderFromFile對著色器檔案進行編譯了
例:HLSL編寫的頂點著色程式,對頂點實施了取景變換(view transformation)和投影變換(projection transformation),並將頂點的漫反射顏色分量設為藍色
// Global variable to store a combined view and projection transformation matrix .We initialize this variable
// from the application
matrix ViewProjMatrix;
//Initialize a global blue color vector
vector Blue = {0.0f,0.0f,1.0f,1.0f};
// Input structure describes the vertex that is input into the shader .Here the input vertex contains a position //componment only
struct VS_INPUT
{
vector position : POSITION;
};
// Output structure describes the vertex that is Output from the shader .Here the output vertex contains a position //and color componment
struct VS_OUTPUT
{
vector position :POSITION;
vector diffuse :COLOR;
};
// Main Entry Point ,observe the main function receives a copy of the input vertex through its parameter and returns //a copy of the output vertex in computes
VS_OUTPUT Main(VS_INPUT input)
{
// zero out members of output
VS_OUTPUT output = (VS_OUTPUT)0;
// transform to view space and project
output.position = mul(input.position,ViewProjMatrix);
// set vertex diffuse color to blue
output.diffuse = Blue;
return output;
}
ViewProjMatrix 是一個matrix類型的執行個體,該類型是HLSL中的內建類型,表示維數4*4,該變數儲存了取景變換矩陣和投影變換矩陣的乘積,這樣它就同時描述了兩種變換,所以,只需進行一次向量矩陣乘法就可實現上述兩種變換,注意,在著色器代碼中時找不到這些變數的初始化代碼的,因為這些變數的初始化應在應用程式原始碼中進行而非著色器程式代碼中,
Blue是HLSL內建類型vector的一個執行個體,該類型表示一個4D向量,只將其視為RGBA顏色向量,並將其初始化為藍色
VS_INPUT 和VS_OUTPUT出現的很特別的冒號(colon)文法(syntax)表達了一種語義(semantic),用來指定變數的用途,這與頂點結構中的靈活頂點格式(FVF)非常相似.
例
vector position:POSITION:
文法":POSITION"的意思是說向量position用於描述輸入頂點的位置資訊
例
vector diffuse :COLOR
":COLOR"意思是說向量diffuse用於描述輸出頂點的顏色資訊
每個HLSL程式也應該有一個進入點,但該名稱並非強制性的,在遵循函數命名的規則的前提下,著色器的入口函數的命名可自由選擇,入口函數必須有一個可接收輸入結構的參數,該參數將用於把輸入頂點傳給著色器,而入口函數必須返回一個輸出結構執行個體,用來將經過處理的頂點自著色器輸出
HLSL著色器程式的編譯
每個著色器都用常量表來儲存其變數,為了使應用程式能夠訪問著色器的常量表(Constant Table),D3DX庫提供了介面ID3DXConstantTable,藉助該介面,可在應用程式原始碼中對著色器代碼中的變數進行設定
擷取常量控制代碼:
當給定著色器中期望引用的那個變數的名稱時,該函數返回一個引用了該變數的D3DXHANDLE類型的控制代碼(Handle)
D3DXHANDLE ID3DXConstantTable::GetConstantByName(
D3DXHANDLE hConstant,// scope of constant
LPCSTR pName // name of constant
);
#hConstant 一個D3DXHANDLE類型的控制代碼,標識了那個包含了希望擷取控制代碼的變數的父結構,例如,希望得到某個特定結構執行個體(instance)的一個單個資料成員的控制代碼,可為該參數傳入該結構執行個體的控制代碼,如果想要擷取指向頂級(top - level)變數的控制代碼,應該將該參數指定為0
#pName希望擷取控制代碼的那個著色器原始碼中的變數名稱
例:如果引用的著色器中的變數名稱為ViewProjMatrix,且該變數為頂級參數,可以這樣實現引用:
// Get a handle to the ViewProjMatrix variable in the shader
D3DXHANDLE h0;
h0 = ConstTable -> GetConstantByName(0,"ViewProjMatrix");
常量的設定:
擷取了變數D3DXHANDLE類型的控制代碼,就可在應用程式中使用方法ID3DXConstantTable::SetXXX對該變數進行設定,XXX表示被設定變數的類型名稱,實際調用時只有用類型名將其替換即可
例:設定變數為一個vector類型的數組,該方法對應SetVectorArray
方法ID3DXConstantTable::SetXXX的通用簽名:
HRESULT ID3DXConstantTable::SetXXX(
LPDIRECT3DDEVICE9 pDevice,//與常量表相關的裝置指標
D3DXHANDLE hConstant,//要設定的那個變數的控制代碼
XXX value //指定引用了那個著色器的變數應被賦何值,XXX替換該變數名 //稱,對某些類型(bool,int float)出入的是該值的副本,而對 //另外一些類型(向量,矩陣結構體),傳入的是指向該值的指標
);
如果對數組進行設定,SetXXX方法還增加了一個參數,以接收該數組的維數,例,設定一個4D向量的數組的方法原型為:
HRESULT ID3DXConstantTable::SetVectorArray(
LPDIRECT3DDEVICE9 pDevice,// associated device
D3DXHANDLE hConstant,// handle to shader variable
CONST D3DXVECTOR4* pVector,// pointer to array
UINT Count// number of elements in array
);
該列表描述了那些可用介面ID3DXConstantTable進行設定的類型,假定已經擁有了一個合法的裝置指標以及所要設定的變數的控制代碼
#SetBool: 設定一個布爾值
bool b = true
ConstTable->SetBool(Device,handle,b);
#SetBoolArray 設定布爾數組
bool b[3] = {true,false,true};
ConstTable->SetBoolArray(Device,handle,b,3);
#SetFloat 設定浮點數
float f = 3.14f;
ConstTable->SetFloat(Device,handle,f);
類似的還有:
#SetFloatArray
#SetInt
#SetIntArray
#SetMatrix設定一個4*4矩陣
D3DXMATRIX M(..);
ConstTable->SetMatrix(Device,handle,&M);
#SetMatrixArray設定4*4矩陣數組
D3DXMATRIX M[4];
ConstTable->SetMatrixArray(Device,handle,M,4);
#SetMatrixPointerArray 設定一個4*4矩陣指標數組
D3DXMATRIX* M[4];
ConstTable->SetMatrixPointerArray(Device,handle,M,4);
#SetMatrixTranspose設定4*4轉置矩陣
D3DXMATRIX M(...);
D3DXMatrixTranspose(&M,&M);
ConstTable->SetMatrixTranspose(Device,handle,&M);
#SetMatrixTransposeArray設定4*4轉置矩陣數組
D3DXMATRIX M[4];
ConstTable->SetMatrixTransposeArray(Device,handle,M,4);
#SetMatrixTransposePointerArray設定4*4轉置矩陣指標數組
D3DXMATRIX* M[4];
ConstTable->SetMatrixTransposePointerArray(Device,handle,M,4);
#SetVector設定一個D3DXVECTOR4類型的變數
D3DXVECTOR4 v(1.0f,2.0f,3.0f,4.0f);
ConstTable->SetVector(Device,handle,&v);
#SetVectorArray設定向量數群組類型的變數
D3DXVECTOR4[3];
ConstTable->SetVectorArray(Device,handle,v,3);
#SetValue設定大小任意的類型,
例,結構體,用該函數對D3DXMATRIX類型的變數進行設定
D3DXMATRIX M(...);
ConstTable->SetValue(Device,handle,(void*)&M,sizeof(M);
設定常量的預設值:即在變數聲明時被賦予的初值,該方法在應用程式的設定過程中,應調用一次
HRESULT ID3DXConstantTable::SetDefaults(LPDIRECT3DDEVICE9 pDevice);
HLSL著色器程式編譯:
該函數對儲存在文字檔中的著色器程式進行編譯
HRESULT D3DXCompileShaderFromFile(
LPCSTR pSrcFile,//儲存了著色器原始碼的那個文字檔的名稱
CONST D3DXMACRO* pDefines,//該參數可選,可設為NULL
LPD3DXINCLUDE pInclude,//指向ID3DXInterface介面的指標,應用程式實現該介面,以重 //載預設的include行為(include behavior),通常預設的include行為已足夠 //滿足要求,可將該參數指定為NULL而將其忽略
LPCSTR pFunctionName,//一個指定了著色器入口函數名稱的字串,例,如果著色 //器的入口函數為Main,該參數應賦為"Main"
LPCSTR pTarget,//指定了要將HLSL原始碼編譯成的著色器版本,該參數為一字串 //,合法的頂點著色器版本有:vs_1_1,vs_2_0,vs_2_sw,合法的像素著色器 //版本有:ps_1_1,ps_1_2,ps_1_3,_ps_2_sw,例,將頂點著色器譯為2.0版本, //則需要將該參數指定為vs_2_0,能將著色器編譯為不同著色器版本的能力是 //HLSL與組合語言相比的一個主要優勢,藉助HLSL,幾乎可以即時地將一個 //著色器移植到不同的版本中
DWORD Flags,可選的編譯選項,若該參數為0,表示不使用任何選項
#D3DXSHANER_DEBUG 指示編譯器寫入調試資訊
#D3DXSHADER_SKIPVALIDATION指示編譯器不要進行任何代碼驗證,僅當正在 //使用一個以確定可用的著色器時,該參數才被使用
#D3DXSHADER_SKIPOPTIMIZATION指示編譯器不要對代碼做任何最佳化,實際上 //,僅在調試時該選項有用,因為調試時不希望編譯器對代碼做任何改動
LPD3DXBUFFER* ppShader,//返回指向介面ID3DXBuffer的指標,該介面包含了編譯後的著色 //器代碼,編譯後的著色器代碼就可作為另一個函數的參數來建立實際的頂點著色器 //或像素著色器
LPD3DXBUFFER* ppErrorMsgs,//返回指向介面ID3DXBuffer指標,包含了錯誤碼和訊息的 //字串
LPD3DXCONSTANTTABLE* ppConstantTable//返回指向介面ID3DXConstantTable的指標,該介面 //包含了該著色器的常量表資料
);
例:
ID3DXConstantTable* TransformConstantTable = 0 ;
ID3DXBuffer* shader = 0;
ID3DXBuffer* errorBuffer = 0 ;
hr = D3DXCompileShaderFromFile(
"transform.txt",// shader filename
0,
0,
"Main",
"vs_2_0",
D3DXSHADER_DEBUG,
&shader,
&errorBuffer,
&TransformConstantTable);
// output any error message
if (errorBuffer)
{
::MessageBox(0,(char*)errorBuffer->GetBufferPointer(),0,0);
d3d::Release<ID3DXBuffer*>(errorBuffer);
}
if (FAILED(hr))
{
::MessageBox(0,"D3DXCreateEffectFromFile() - FAILED",0,0);
return false;
}
變數類型(scalar type)
#bool 布爾值,true,false
#int 32位有符號整數
#half 16位浮點數
#float 32位浮點數
#double 64位浮點數
注意,有些平台可能不支援int ,half和double,這些類型將用float來類比
HLSL內建向量類型(vector type)
#vector 一個4D向量,其中每個元素的類型都是float
#vector<T,n>一個n維向量,其中每個元素的類型均為標量類型T,維數n必須介於1-4之間
例,一個二維double型向量例子
vector<double,2>vec2;
HLSL特殊文法--"替換調配(swizzles)"來專門用來完成這類不關心順序的複製操作
vector u = {1.0f,2.0f,3.0f,4.0f};
vector v = {0.0f,0.0f,0.0f,0.0f};
v = u.xyyw
有選擇地複製
例:可僅複製x和y分量
vector u = {1.0f,2.0f,3.0f,4.0f};
vector v = {0.0f,0.0f,0.0f,0.0f};
v.xy = u//v = {1.0f,2.0f,0.0f,0.0f};
HLSL內建矩陣類型(matrix type)
#matrix 表示一個4*4矩陣,該矩陣中每個元素類型均為float
#matrix<T,m,n>表示一個m*n矩陣,其中的每個元素都為標量類型T,該矩陣的維數m和n必須介於1-4之間
例,表示一個2*2的整型矩陣
matrix<int ,2,2>m2x2;
還可用該文法來定義m*n矩陣,其中m和n必須介於1-4之間
floatmxn matmxnl
例:
float2x2 mat2x2;
float3x3 mat3x3;
float4x4 mat4x4;
float2x4 mat2x4;
整型矩陣
int2x2 i2x2;
可用數組的雙下標文法來訪問矩陣的各項(entry,元素),例如,要對矩陣M中第i行,第j列的項進行設定
M[i][j] = value;
還可用像訪問結構體中的成員那樣訪問矩陣M中的項,HLSL已定義了下列項名稱
下標從1開始的情形
M._11=M._12=M._13=M._14 = 0.0f;
M._21=M._22=M._23=M._24 = 0.0f;
M._31=M._32=M._33=M._34 = 0.0f;
M._41=M._42=M._43=M._44 = 0.0f;
下標從0開始的情形
M._m00=M._m01=M._m02=M._m03 = 0.0f;
M._m10=M._m11=M._m12=M._m13 = 0.0f;
M._m20=M._m21=M._m22=M._m23 = 0.0f;
M._m30=M._m31=M._m32=M._m33 = 0.0f;
引用矩陣中某一特定行,可通過數組單下標文法來實現
例,引用矩陣M的第i行
vector ithRow =M[i];
對HLSL中變數進行初始化
vector u ={0.6f,0.3f,1.0f,1.0f};
vector v ={1.0f,5.0f,0.2f,1.0f};
也可用建構函式文法:
vector u = vector(0.6f,0.3f,1.0f,1.0f);
vector v = vector(0.6f,0.3f,1.0f,1.0f);
float2x2 f2x2 = float2x2(0.6f,0.3f,1.0f,1.0f);
int2x2 m = {1,2,3,4};
int a = {5};
float3 x = float3{0,0,0};
HLSL的數組;
float M[4][4];
half p[4];
vector v[12];
HLSL結構體定義與c++相同,但是HLSL中結構體不允許有成員函數
HLSL關鍵字typedef 與c++的功能完全一樣
例,可用文法類型vector<float,3>賦予另一個名稱point:
typedef vector<float,3> point;
vector<float,3>myPoint 等價於point myPoint;
對常量類型和數群組類型運用關鍵字typedef
typedef const float CFLOAT;
typedef float point2[2];
HLSL變數的首碼:
#staitc 如果全域變數在聲明時使用了關鍵字static,該變數在該著色器程式外部可見
#uniform 如果變數聲明時使用了關鍵字uniform,表明該變數將在著色器之外進行初始化,例如,在c++程式中對該變數進行初始化,然後再作為輸入傳給該著色器
#extern 表明該變數可在該著色器程式之外進行訪問,只有全域變數可使用關鍵字extern,非靜態全域變數在預設狀態下都是extern類型的。
#shared提示效果架構該變數可在多個效果之間共用,只有全域變數方可使用
#volatile提示效果架構該變數將經常被修改,只有全域變數方可使用
const HLSL中和C++中含義完全相同
HLSL支援許多與c++類似的語句,如選擇,迴圈和一般的順序流程
HLSL支援一種靈活的類型轉換機制,HLSL中的類型轉換文法與c語言的完全相同
例將float類型轉換為matrix類型
float f = 5.0f
matrix m = (matrix)f;
HLSL支援許多與c++類似的運算子,但仍有一些差別,首先,模數運算子%適用於整型和浮點型資料,適用模數運算子時,左運算元和右運算元必須同號,其次,許多HLSL運算都是在變數的分量級(component basis)上進行的,這是由於向量和矩陣都是HLSL的內建類型,而這些類型都由若干分量組成,由於有了能夠在分量級上進行運算的運算子,如向量/矩陣加法,向量/矩陣加法,向量/矩陣的相等測試等運算級可使用與標量類型相同的運算子來進行
例:
vector u = {1.0f,0.0f,-3.0f,1.0f};
vector u = {-4.0f,2.0f,1.0f,0.0f};
//adds corresponding components
vector sum = u+v;// sum = (-3.0f,2.0f,-2.0f,1.0f)
向量自增也是每個向量自增
sum++;//sum = (-2.0f,3.0f,-1.0f,2.0f)
向量的逐分量相乘
vector u = {1.0f,0.0f,-3.0f,1.0f};
vector v = {-4.0f,2.0f,1.0f,0.0f};
vector sum = u*v;// sum = (-4.0f,0.0f,-3.0f,0.0f)
比較子也是在分量上操作的,並將返回一個bool型的向量或矩陣(其每個分量的類型均為bool),返回"bool"向量包含了兩個分量的比較結果
vector u = {1.0f,0.0f,-3.0f,1.0f};
vector v = {-4.0f,0.0f,1.0f,1.0f};
vector b = (u == v);//b = (false ,true,false ,true)
最後,討論雙目運算中的變數型別提升(variable promotion)
#對於雙目運算,如果左運算元與右運算元的維數不同,則維數較小的運算元得到提升(類型轉換),使得其維數與原先維數較大的運算元相同
#對於雙目運算,如果左右運算元類型不同,則具有較低類型的運算元得到提升(類型轉換),使得其類型的精度與原先具有較高類型的精度的運算元相同
HLSL中的函數具有下列性質:
#函數使用與C++類似的文法
#參數總是按值傳遞的
#不支援遞迴
#函數總是內聯的(inline)
HLSL還額外增加了一些專門在函數中使用的關鍵字
in //指定在該函數執行之前,必須對該形參傳入實參的副本,可以不顯式指定in,預設狀態下每個形參都是in類型
out//指定當函數返回時,形參的值將複製給實參,out僅用於輸出資料
inout//該參數即可作為輸入又可作為輸出
HLSL擁有一個豐富的內建函數集
注意如果為一個"標量"函數(即一般只對標量進行運算的函數,如cos(x))傳入一個非標量類型的參數,該函數將針對該傳入參數的每個分量進行計算。
例:
float3 v = float3(0.0f,0.0f,0.0f);
v = cos(v);// v = (cos(x),cos(y),cos(z))
總結 :
1 HLSL程式一般儲存在ASCII文字檔中,其編譯應由應用程式調用函數D3DXCompileShaderFromFile來完成
2 介面ID3DXConstantTable允許在應用程式中對著色器程式中的變數進行設定,這種資料通訊時必要的,因為著色器所使用的資料在程式繪製的每一幀中都有可能發生變化,例如,應用程式的取景變換矩陣發生了改變,就需要用新的取景變換矩陣來更新著色器中的取景變換矩陣,藉助ID3DXConstantTable介面,就可實現資料更新
3 對於每個著色器,必須定義兩個能夠分別描述著色器輸入資料和輸出資料格式的輸入結構和輸出結構
4 每個著色器都有一個入口函數,該函數接收一個用於將輸入資料傳遞給著色器的輸入結構類型的參數,此外,每個著色器都返回一個輸出結構執行個體,以便將處理結果輸出.