遊戲開發基礎(十六)

第十六章
高級着色語言: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 每個着色器都有一個入口函數,該函數接收一個用於將輸入數據傳遞給着色器的輸入結構類型的參數,此外,每個着色器都返回一個輸出結構實例,以便將處理結果輸出.

發佈了79 篇原創文章 · 獲贊 17 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章