1. 程式人生 > >DirectX11 高階著色器語言HLSL入門

DirectX11 高階著色器語言HLSL入門

高階著色器語言HLSL入門

1. 資料型別簡介

與CPU不同,在顯示卡晶片中,最小的資料吞吐單元是一個由32位浮點陣列成的四元組。這一點很有道理不是,想想你在渲染過程中所有涉及到的資料,最複雜的不外乎四維座標(x,y,z,w)或顏色(r,g,b,a),這樣GPU可以一次性處理一個四元組。而整數什麼的在顯示卡中被放到四元組的一個分量裡使用,而很多顯示卡中,整數、布林值都不被直接支援,而是轉為浮點數使用。至於矩陣,通常用4個四元組表示一個4x4矩陣(預設情況一個四元組儲存一行,也可以指定按列儲存,屬於細節問題,goto:細節問題)其他尺寸的以此類推。
反映到程式上,一個四維向量就被宣告為float4,4維方矩陣被宣告為float4x4等等。當然,你也可以使用任意不超過4的維度的向量或矩陣,如int3,float3x3,double1。這個double1實際上就是標量了,1可以省略不寫。

2. 紋理(Texture)&取樣器(Sampler)簡介

這倆東西可以看作特殊型別變數。紋理就是Shader中用到的貼圖資源,這我想沒什麼好說的。來解釋一下取樣器:實際上每張貼圖在使用的時候都要用一個取樣器。取樣器相當於這樣一個結構,除了儲存貼圖本身資料之外,還包括過濾引數等取樣資訊。通常,讀取貼圖這樣的指令接收的都是取樣器型別的引數而並非直接接收紋理貼圖。宣告及使用紋理或取樣器跟使用普通變數一樣。這裡有一些初始化取樣器的方法,還是等到後面的例項中講述吧。

3. 資料型別大全

資料型別有值型別、向量、矩陣、取樣器、和結構體。
1.值型別
  bool 布林變數
  half 16為整形
  int 32位整形
  float 單精度浮點數
  double 雙精度浮點數
  宣告方式:float f;
  賦值方式:f = 1;
2.向量
  宣告方式:float4 f;
  賦值方式:f = {1,2,3,4};
  取值方式:float3 ff = f.rgb;
  說明:向可以通過xyzw或者rgba訪問向量中的指定欄位,x或者r就是代表0號欄位。不僅可以單獨操作一個欄位,還可以對多個欄位同時操作,例如3*f.xyz,就是將f中的xyz都乘以個3。
3.矩陣
  宣告方式:float2x4 f; 先行後列。
  賦值方式:f = {1,1,2,2,3,3,4,4};
  取值方式:float ff = f[0][0];
  說明:如果要對矩陣做乘法運算,請使用mul函式,如mul(ff,f)。
4.取樣器
  宣告方式:
    texture Texture; //紋理變數
    sampler TextureSampler = sampler_state //紋理取樣器
    {
    Texture = ; //紋理取樣器使用的紋理物件
    MinFilter = Linear; //縮小圖形使用線性濾波
    MagFilter = Linear; //放大圖形使用線性濾波
    MipFilter = Linear; //Mipmap使用線性濾波
    AddressU = Wrap; //U方向上的紋理定址模式採用Wrap方式
    AddressV = Wrap; //V方向上的紋理定址模式採用Wrap方式
    };
  賦值方式:在C#中對Texture賦值,effect.Parameters[“Texture”].SetValue(Game.Content.Load(“*”));
  取值方式:tex2D(TextureSampler, TEXCOORD0);
  說明:MinFilter、MagFilter、MipFilter、AddressU、AddressV是可選項,如果不寫將會使用預設值,也就是上面賦予的值。
5.結構體
  宣告方式:
    struct VertexShaderInput
    {
      float4 Position : POSITION;
      float2 TextureCoordinates : TEXCOORD0;
      float3 Normal: NORMAL;
    };
    VertexShaderInput input;
    此處與C#語法有些區別,直接這麼寫,不需要再寫個new什麼的。
  賦值方式:與C#語法一致。
  取值方式:與C#語法一致。

4. 控制流

控制流,就是if…else,for,while什麼的。在CPU中,這些控制流造成的實際上是指令跳轉。但在GPU中指令跳轉並不被廣泛的支援,以往的大部分顯示卡只懂得按順序一句一句執行指令。因此HLSL的編譯器可能會做出諸如展開迴圈、遍歷分支等等莽撞的事來適應顯示卡。所以使用時要特別小心,而且不是所有情況的控制流語句都被支援。具體的很多規則還是在細節問題裡。

5. 函式

HLSL中提供了很多函式可供呼叫,在Direct3D 文件 -> DirectX Graphics -> Reference -> HLSL Shader Reference -> HLSL Intrinsic Functions中有這些函式的詳細列表。也可自己寫函式用,但是在較早的Shader版本中,就像行內函數一樣編譯時最終要將函式展開插入到函式呼叫處。還有一點我想你一定會想到的就是主函式會是什麼。Vertex Shader和Pixel Shader各自需要一個主函式,由程式設計師來指定!沒錯,程式設計師在Shader外部指定。
HLSL 中的函式定義與其它程式語言中的完全一樣:

ReturnValue FunctionName( parameterName : semantic )
{
// function code goes here
}

函式的返回值可以是任何 HLSL 中定義的型別,包括組合型別和 void 空型別。 當你定義一個著色器函式的引數列表時,需要完全指定跟隨變數之後的語義標識。 當定義函式引數時,還有一些事項需要注意,因為 HLSL 沒有具體的方式用於返回引數列表中的值的引用,這需要通過定義少量的關鍵字來達成同樣的結果。
在引數宣告之前,使用關鍵字 out 可以讓編譯器知道該變數可以用於輸出。另外地,關鍵字 inout 可以允許變數既作為輸入也作為輸出:

void GetColor( out float3 color )
{
    color = float3( 0.0f, 1.0f, 1.0f );
}

6. 頂點著色器

當物體通過管線傳輸需要繪製時,它們的頂點會發送到
你的頂點著色器中處理。 如果你不想對傳入的頂點做任何處理,可以直接將它們傳送給畫素著色器進行繪製。 大多數情況,你至少需要使用一個世界或投影變換作用這些頂點,以使得它們在正確的空間位置被渲染。
使用頂點著色器,你可以對頂點做諸多的控制,而不僅僅是進行簡單的變換。可以將頂點平移到任何座標軸,改變它的顏色,或任何其它性質的控制。

PS_Input VS_Main( VS_Input vertex )
{
    PS_Input vsOut = ( PS_Input )0;
    vsOut.pos = vertex.pos;
    vsOut.tex0 = vertex.tex0;
    return vsOut;
}

這個頂點著色器看起來像C語言的函式。HLSL使用類似C語法,對C/C++瞭解後,學習HLSL更容易。我們可以看到頂點渲染器,名字叫做VS,以float4為引數,返回值為float4。在HLSL中,float4是又4個浮點陣列成的。那個冒號後面定義的是引數的semantics,返回值也是如此。就像之前提到的,semantics在HLSL中描述的是資料的屬性。在上面的渲染器中,我們選擇POSITION作為輸入引數Pos的語義,因為這個引數包含了頂點座標資訊。返回的語義是SV_POSITION(SV是System Value的縮寫)。SV_POSITION是事先已經定義好的語義,畫素著色器的頂點語義不是POSITION,而是SV_POSITION,這兩種語義的區別在於SV表示硬體會進行插值。SV_POSITION帶入PS的,和你自己在畫素著色器裡做透視除法的結果一樣。見頂點著色器的輸入結構和畫素著色器的輸入結構:

struct VS_Input
{
    float4 pos : POSITION;
    float2 tex0 : TEXCOORD0;
};
struct PS_Input
{
    float4 pos : SV_POSITION;
    float2 tex0 : TEXCOORD0;
};

7. 畫素著色器

現代計算機的顯示器通常顯示速度很快,螢幕的最小單位就是畫素。每個畫素都有一個顏色,並且每個畫素相互獨立。當我們要在螢幕上渲染一個三角形時,我們並不是把整個三角形當做一個實體來畫。其實是,我們把三角形區域的畫素繪製出來。
光柵化
將三角形的三個頂點所覆蓋的一串畫素繪製出來的操作叫做光柵化。GPU首先要判斷哪些畫素被三角形區域覆蓋。然後GPU呼叫啟用的畫素渲染器渲染這些畫素。一個畫素渲染器的主要目標就是計算每個畫素的顏色。渲染器根據輸入來計算頂點顏色,或者,如果沒有使用幾何渲染器,就像在這個教程中,畫素渲染器的輸入將直接來自頂點渲染器的輸出。
畫素著色器可以讓你訪問任何經過管線輸出之前的畫素。 在畫素被繪製到螢幕之前,你有機會改變每個畫素的顏色。 某些情況,你只需要簡單的返回由頂點或幾何著色器傳入的畫素顏色,但是大多數情況,你需要處理光照或貼圖對畫素顏色的影響。

float4 PS_Main( PS_Input frag ) : SV_TARGET
{
return colorMap_.Sample( colorSampler_, frag.tex0 );
}

上面的函式中,SV_TARGET是返回值得語義,它表示一個輸出語義,用於指定畫素著色器的用於渲染目標的輸出。

8. 語義(semantic)

語義名字(semantic name),是一個描述元素目的的字串。例如元素作為頂點的位置,則它的語義就是“ POSITION”。我們可以使該元素通過語義“ COLOR”用於頂點顏色,通過“ NORMAL”用於法線向量等等。語義使得元素繫結一個 HLSL 著色器作為它的輸入或輸出變數。
另外,我們必須更新頂點著色器的輸入結構和畫素著色器的輸入結構來允許使用貼圖座標。頂點著色器將會獲得來自於頂點快取塊的貼圖座標並且直接將它們傳遞給畫素著色器,使得畫素著色器來訪問它們。

struct VS_Input
{
    float4 pos : POSITION;
    float2 tex0 : TEXCOORD0;
};

一些公共的語義包括:
SV_POSITION——一個具體的變換位置的 float4 值
NORMAL0——定義一個法線向量
COLOR0——定義一個顏色值
還有一些其他的語義見於 DirectX SDK 文件的 HLSL 章節的完全列表。其中大量的語義末尾跟隨一個數字,因為可能定義多個這樣的語義型別。

9. register

Texture2D colorMap_ : register( t0 );
SamplerState colorSampler_ : register( s0 );

物件 colorMap_是Texture2D 型別,因為它用於 2D 貼圖,而 colorSampler_是 HLSL 高階著色語言的一個型別 SamplerState。為了在我們提供的渲染函式中的著色器輸入中繫結這些物件,我們必須使用 HLSL 註冊關鍵字 register。 為了繫結第一個輸入貼圖我們使用 t0,這裡他表示貼圖型別, 0 表示使用第一個索引貼圖。對於採用狀態物件使用 s0 出於同樣的原因。因為我們使用函式 PSSetSamplers 和 PSSetShaderResource 來傳遞一個數組元素給我們的著色器使用,所以必須將我們使用的資料索引繫結給每一個 HLSL 變數。因為我們只有一張貼圖和一個取樣狀態,我們只需要使用 t0 和 s0 即可。
需要注意的是,這些註冊的快取必須符合我們在渲染函式 Render 中指定的那樣,才能將快取正確的註冊到 HLSL 物件中。

10. 細節問題

你會覺得前面說的太過粗略,還有很多問題沒有敘述,但相對來講這些都算是細枝末節了。例如HLSL中保留關鍵字有哪些;變數的作用域;資料型別的詳細資訊;四元組分量的使用法則等等,這些在Direct3D文件 -> DirectX Graphics -> Programming Guide -> The Programmable Pipeline -> Programmable HLSL Shaders -> HLSL Language Basics中講得比我清楚,我也不再多餘翻譯了。