在 Direct3D 9 撰寫 HLSL 著色器

Vertex-Shader基本概念

在作業中時,可程式化的頂點著色器會取代 Microsoft Direct3D 圖形管線所完成的頂點處理。 使用頂點著色器時,固定函式管線會忽略轉換和光源作業的相關狀態資訊。 停用頂點著色器並傳回固定函式處理時,會套用所有目前的狀態設定。

在頂點著色器執行之前,應該先進行高階基本類型的鑲嵌。 在著色器處理之後執行表面鑲嵌的實作,必須以應用程式和著色器程式碼看不見的方式執行。

頂點著色器至少必須在同質裁剪空間中輸出頂點位置。 或者,頂點著色器可以輸出紋理座標、頂點色彩、頂點光源、霧因數等等。

Pixel-Shader基本概念

圖元處理是由個別圖元上的圖元著色器執行。 圖元著色器可與頂點著色器搭配運作;頂點著色器的輸出會提供圖元著色器的輸入。 其他圖元作業 (陰影混合、樣板作業和轉譯目標混合) 執行著色器之後發生。

紋理階段和取樣器狀態

圖元著色器會完全取代多重紋理混合器所指定的圖元混合功能,包括先前由紋理階段狀態定義的作業。 紋理取樣和篩選作業是由標準紋理階段狀態所控制,用於縮小、放大、mip 篩選和包裝定址模式,可以在著色器中初始化。 應用程式可以自由地變更這些狀態,而不需要重新產生目前系結著色器。 如果您的著色器是在效果內設計,則可以更輕鬆地設定狀態。

圖元著色器輸入

對於圖元著色器版本ps_1_1 - ps_2_0,擴散和反射色彩會在著色器使用之前,在範圍 0 到 1 中 (固定) 飽和。

系統會假設圖元著色器的色彩值輸入正確,但這並不保證 (所有硬體) 。 從紋理座標取樣的色彩會以正確的方式逐一查看,並在反復專案期間限制為 0 到 1 範圍。

圖元著色器輸出

對於圖元著色器版本ps_1_1 - ps_1_4,圖元著色器發出的結果是暫存器 r0 的內容。 當著色器完成處理時,無論它包含什麼,都會傳送至水階段和轉譯目標混合器。

對於圖元著色器版本ps_2_0和更新版本,輸出色彩會從 oC0 - oC4 發出。

著色器輸入和著色器變數

宣告著色器變數

最簡單的變數宣告包含類型和變數名稱,例如這個浮點宣告:

float fVar;

您可以在相同的 語句中初始化變數。

float fVar = 3.1f;

可以宣告變數陣列,

int iVar[3];

或在相同的語句中宣告並初始化。

int iVar[3] = {1,2,3};

以下是幾個宣告,示範高階著色器語言 (HLSL) 變數的許多特性:

float4 color;
uniform float4 position : POSITION; 
const float4 lightDirection = {0,0,1};

資料宣告可以使用任何有效的類型,包括:

著色器可以有最上層變數、引數和函式。

// top-level variable
float globalShaderVariable; 

// top-level function
void function(
in float4 position: POSITION0 // top-level argument
              )
{
  float localShaderVariable; // local variable
  function2(...)
}

void function2()
{
  ...
}

最上層變數會在所有函式之外宣告。 最上層引數是最上層函式的參數。 最上層函式是應用程式所呼叫的任何函式 (,而不是另一個函式所呼叫的函式) 。

統一著色器輸入

頂點和圖元著色器接受兩種輸入資料:不同且統一。 不同的輸入是著色器每個執行的唯一資料。 針對頂點著色器,不同的資料 (例如:位置、標準等等) 來自頂點資料流程。 例如,統一資料 (:材質色彩、世界轉換等) 是著色器多次執行的常數。 對於熟悉元件著色器模型的模型,統一資料是由常數暫存器指定,並由 v 和 t 暫存器來改變數據。

統一資料可由兩種方法指定。 最常見的方法是宣告全域變數,並在著色器內使用這些變數。 著色器內的任何全域變數使用都會將該變數新增至該著色器所需的統一變數清單。 第二種方法是將最上層著色器函式的輸入參數標示為統一。 此標記指定應將指定的變數新增至統一變數清單。

著色器所使用的統一變數會透過常數資料表傳回應用程式。 常數資料表是符號資料表的名稱,可定義著色器所使用的統一變數如何放入常數暫存器中。 與全域變數不同,統一函式參數會出現在前面加上貨幣符號 ($) 的常數資料表中。 需要貨幣符號,才能避免本機統一輸入與相同名稱的全域變數之間的名稱衝突。

常數資料表包含著色器使用之所有統一變數的常數暫存器位置。 如果指定,資料表也包含類型資訊和預設值。

不同的著色器輸入和語意

最上層著色器函式 (不同的輸入參數) 必須以語意或統一關鍵字標示,指出執行著色器的值是常數。 如果最上層著色器輸入未以語意或統一關鍵字標示,著色器將無法編譯。

輸入語意是用來將指定輸入連結至圖形管線上一個部分輸出的名稱。 例如,頂點著色器會使用輸入語意 POSITION0 來指定頂點緩衝區的位置資料應該連結的位置。

圖元和頂點著色器有不同的輸入語意集,因為圖形管線的不同部分會饋送至每個著色器單位。 頂點著色器輸入語意描述每個頂點資訊 (例如:位置、一般、紋理座標、色彩、正切值、二進位檔等。) 從頂點緩衝區載入到頂點著色器可以使用的表單。 輸入語意會直接對應至頂點宣告使用方式和使用方式索引。

圖元著色器輸入語意描述點陣化單位為每個圖元提供的資訊。 資料是藉由插補目前基本類型之每個頂點的頂點著色器的輸出來產生。 基本圖元著色器輸入語意會將輸出色彩和紋理座標資訊連結至輸入參數。

輸入語意可以透過兩種方法指派給著色器輸入:

  • 將冒號和語意名稱附加至參數宣告。
  • 使用指派給每個結構成員的輸入語意定義輸入結構。

頂點和圖元著色器會將輸出資料提供給後續的圖形管線階段。 輸出語意可用來指定著色器所產生的資料應該如何連結到下一個階段的輸入。 例如,頂點著色器的輸出語意是用來連結點陣化中插補器的輸出,以產生圖元著色器的輸入資料。 圖元著色器輸出是提供給每個轉譯目標的 Alpha 混合單位的值,或寫入深度緩衝區的深度值。

頂點著色器輸出語意可用來將著色器連結至圖元著色器和轉譯器階段。 點陣化所取用的頂點著色器,而且不會公開給圖元著色器,必須至少產生位置資料。 產生紋理座標和色彩資料的頂點著色器會在完成插補之後,將該資料提供給圖元著色器。

圖元著色器輸出語意會將圖元著色器的輸出色彩與正確的轉譯目標系結。 圖元著色器輸出色彩會連結至 Alpha 混合階段,決定目的地轉譯目標如何修改。 圖元著色器深度輸出可用來變更目前點陣位置的目的地深度值。 只有某些著色器模型才支援深度輸出和多個轉譯目標。

輸出語意的語法與指定輸入語意的語法相同。 語意可以直接在宣告為 「out」 參數的參數上指定,或在傳回為 「out」 參數或函式傳回值的結構定義期間指派。

語意可識別資料的來源。 語意是可識別著色器輸入和輸出的選擇性識別碼。 語意會出現在三個位置的其中一個:

  • 結構成員之後。
  • 在函式輸入引數清單中引數之後。
  • 在函式的輸入引數清單之後。

本範例使用 結構來提供一或多個頂點著色器輸入,另一個結構則提供一或多個頂點著色器輸出。 每個結構成員都會使用語意。

vector vClr;

struct VS_INPUT
{
    float4 vPosition : POSITION;
    float3 vNormal : NORMAL;
    float4 vBlendWeights : BLENDWEIGHT;
};

struct VS_OUTPUT
{
    float4  vPosition : POSITION;
    float4  vDiffuse : COLOR;

};

float4x4 mWld1;
float4x4 mWld2;
float4x4 mWld3;
float4x4 mWld4;

float Len;
float4 vLight;

float4x4 mTot;

VS_OUTPUT VS_Skinning_Example(const VS_INPUT v, uniform float len=100)
{
    VS_OUTPUT out;

    // Skin position (to world space)
    float3 vPosition = 
        mul(v.vPosition, (float4x3) mWld1) * v.vBlendWeights.x +
        mul(v.vPosition, (float4x3) mWld2) * v.vBlendWeights.y +
        mul(v.vPosition, (float4x3) mWld3) * v.vBlendWeights.z +
        mul(v.vPosition, (float4x3) mWld4) * v.vBlendWeights.w;
    // Skin normal (to world space)
    float3 vNormal =
        mul(v.vNormal, (float3x3) mWld1) * v.vBlendWeights.x + 
        mul(v.vNormal, (float3x3) mWld2) * v.vBlendWeights.y + 
        mul(v.vNormal, (float3x3) mWld3) * v.vBlendWeights.z + 
        mul(v.vNormal, (float3x3) mWld4) * v.vBlendWeights.w;
    
    // Output stuff
    out.vPosition    = mul(float4(vPosition + vNormal * Len, 1), mTot);
    out.vDiffuse  = dot(vLight,vNormal);

    return out;
}

輸入結構會識別頂點緩衝區中的資料,以提供著色器輸入。 此著色器會將頂點緩衝區的位置、正常和混合重量元素的資料對應至頂點著色器暫存器。 輸入資料類型不需要完全符合頂點宣告資料類型。 如果它不完全相符,頂點資料會在寫入著色器暫存器時自動轉換成 HLSL 的資料類型。 例如,如果一般資料被應用程式定義為 UINT 類型,當著色器讀取時,它會轉換成 float3。

如果頂點資料流程中的資料包含的元件少於對應的著色器資料類型,則遺漏的元件將會初始化為 0 (,但 w 除外,這會初始化為 1) 。

輸入語意類似于 D3DDECLUSAGE中的值。

輸出結構會識別位置和色彩的頂點著色器輸出參數。 管線會使用這些輸出在基本處理) 中,用於三角形點陣化 (。 標示為位置資料的輸出代表頂點在同質空間中的位置。 頂點著色器至少必須產生位置資料。 螢幕空間位置會在頂點著色器完成之後計算,方法是將 x、y、z (x、y、z) 座標除以 w。 在螢幕空間中,-1 和 1 是檢視區界限的最小值和最大值 x 和 y 值,而 z 則用於 z 緩衝區測試。

輸出語意也類似于 D3DDECLUSAGE中的值。 一般而言,頂點著色器的輸出結構也可以做為圖元著色器的輸入結構,前提是圖元著色器不會從標示位置、點大小或模糊語意的任何變數讀取。 這些語意會與圖元著色器未使用的每個頂點純量值相關聯。 如果圖元著色器需要這些值,則可以複製到另一個使用圖元著色器語意的輸出變數。

全域變數會指派給編譯器自動註冊。 全域變數也稱為統一參數,因為每次呼叫著色器時,變數的內容都相同。 暫存器包含在常數資料表中,可使用 ID3DXConstantTable 介面來讀取。

圖元著色器的輸入語意會將值對應至特定硬體暫存器,以用於頂點著色器和圖元著色器之間的傳輸。 每個暫存器類型都有特定屬性。 由於目前只有兩個色彩和紋理座標的語意,因此大部分的資料即使不是,仍會標示為紋理座標。

請注意,頂點著色器輸出結構會使用具有位置資料的輸入,而圖元著色器不會使用此輸入。 HLSL 允許頂點著色器的有效輸出資料,該頂點著色器不是圖元著色器的有效輸入資料,前提是它未在圖元著色器中參考。

輸入引數也可以是陣列。 語意會自動由編譯器針對陣列的每個元素遞增。 例如,請考慮下列明確宣告:

struct VS_OUTPUT
{
    float4 Position   : POSITION;
    float3 Diffuse    : COLOR0;
    float3 Specular   : COLOR1;               
    float3 HalfVector : TEXCOORD3;
    float3 Fresnel    : TEXCOORD2;               
    float3 Reflection : TEXCOORD0;               
    float3 NoiseCoord : TEXCOORD1;               
};

float4 Sparkle(VS_OUTPUT In) : COLOR

上述明確宣告相當於下列宣告,其語意會自動由編譯器遞增:

float4 Sparkle(float4 Position : POSITION,
                 float3 Col[2] : COLOR0,
                 float3 Tex[4] : TEXCOORD0) : COLOR0
{
   // shader statements
   ...

就像輸入語意一樣,輸出語意會識別圖元著色器輸出資料的資料使用量。 許多圖元著色器只會寫入一個輸出色彩。 圖元著色器也可以同時將深度值寫到一或多個轉譯目標中, (最多四個) 。 就像頂點著色器一樣,圖元著色器會使用 結構傳回多個輸出。 此著色器會將 0 寫入色彩元件,以及寫入深度元件。

struct PS_OUTPUT
{
    float4 Color[4] : COLOR0;
    float  Depth  : DEPTH;
};

PS_OUTPUT main(void)
{
    PS_OUTPUT out;

   // Shader statements
   ...

  // Write up to four pixel shader output colors
  out.Color[0] =  ...
  out.Color[1] =  ...
  out.Color[2] =  ...
  out.Color[3] =  ...

  // Write pixel depth 
  out.Depth =  ...

    return out;
}

圖元著色器輸出色彩的類型必須是 float4。 撰寫多個色彩時,所有輸出色彩都必須連續使用。 換句話說,除非已撰寫 COLOR0否則 COLOR1 不能是輸出。 圖元著色器深度輸出的類型必須是 float1。

取樣器和紋理物件

取樣器包含取樣器狀態。 取樣器狀態會指定要取樣的紋理,並控制取樣期間完成的篩選。 取樣紋理需要三件事:

  • 紋理
  • 具有取樣器狀態) 的取樣器 (
  • 取樣指示

取樣器可以使用紋理和取樣器狀態初始化,如下所示:

sampler s = sampler_state 
{ 
  texture = NULL; 
  mipfilter = LINEAR; 
};

以下是取樣 2D 紋理的程式碼範例:

texture tex0;
sampler2D s_2D;

float2 sample_2D(float2 tex : TEXCOORD0) : COLOR
{
  return tex2D(s_2D, tex);
}

紋理是以紋理變數 tex0 宣告。

在此範例中,會宣告名為 s_2D 的取樣器變數。 取樣器包含大括弧內的取樣器狀態。 這包括將取樣的紋理,以及選擇性地篩選狀態 (,也就是包裝模式、篩選模式等) 。 如果省略取樣器狀態,則會套用預設取樣器狀態,以指定紋理座標的線性篩選和換行模式。 取樣器函式會採用雙元件浮點紋理座標,並傳回雙元件色彩。 這會以 float2 傳回型別表示,並代表紅色和綠色元件中的資料。

定義四種類型的取樣器 (請參閱 關鍵字) 和紋理查閱是由內建函式執行: tex1D (s、 t) (DirectX HLSL) tex2D (s, t) (DirectX HLSL ) , tex3D (s, t) (DirectX HLSL) texCUBE (s, t) (DirectX HLSL) . 以下是 3D 取樣的範例:

texture tex0;
sampler3D s_3D;

float3 sample_3D(float3 tex : TEXCOORD0) : COLOR
{
  return tex3D(s_3D, tex);
}

此取樣器宣告會針對篩選設定和位址模式使用預設取樣器狀態。

以下是對應的 Cube 取樣範例:

texture tex0;
samplerCUBE s_CUBE;

float3 sample_CUBE(float3 tex : TEXCOORD0) : COLOR
{
  return texCUBE(s_CUBE, tex);
}

最後,以下是 1D 取樣範例:

texture tex0;
sampler1D s_1D;

float sample_1D(float tex : TEXCOORD0) : COLOR
{
  return tex1D(s_1D, tex);
}

由於執行時間不支援 1D 紋理,因此編譯器會使用 2D 紋理,並瞭解 y 座標不重要。 由於 tex1D (s,t) (DirectX HLSL) 實作為 2D 紋理查閱,因此編譯器可以有效率的方式選擇 y 元件。 在某些罕見的情況下,編譯器無法選擇有效率的 y 元件,在此情況下會發出警告。

texture tex0;
sampler s_1D_float;

float4 main(float texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float, texCoords);
}

這個特定範例沒有效率,因為編譯器必須將輸入座標移至另一個暫存器 (因為 1D 查閱實作為 2D 查閱,而且紋理座標會宣告為 float1) 。 如果使用 float2 輸入而非 float1 重寫程式碼,編譯器可以使用輸入紋理座標,因為它知道 y 已初始化為某個專案。

texture tex0;
sampler s_1D_float2;

float4 main(float2 texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float2, texCoords);
}

所有紋理查閱都可以附加 「bias」 或 「proj」 (,也就是 tex2Dbias (DirectX HLSL ) 、 texCUBEproj (DirectX HLSL) ) 。 使用 「proj」 尾碼,紋理座標會除以 w 元件。 使用「偏差」時,mip 層級會由 w 元件轉移。 因此,所有具有後置詞的紋理查閱一律會採用 float4 輸入。 tex1D (s、t) (DirectX HLSL) tex2D (s、t) (DirectX HLSL) 分別忽略 yz 和 z 元件。

取樣器也可以在陣列中使用,雖然目前沒有後端支援取樣器動態陣列存取。 因此,下列專案有效,因為它可以在編譯時期解析:

tex2D(s[0],tex)

不過,此範例無效。

tex2D(s[a],tex)

取樣器動態存取主要用於撰寫具有常值迴圈的程式。 下列程式碼說明取樣器陣列存取:

sampler sm[4];

float4 main(float4 tex[4] : TEXCOORD) : COLOR
{
    float4 retColor = 1;

    for(int i = 0; i < 4;i++)
    {
        retColor *= tex2D(sm[i],tex[i]);
    }

    return retColor;
}

注意

使用 Microsoft Direct3D 偵錯執行時間可協助您捕捉紋理中元件數目與取樣器之間的不符。

 

撰寫函式

函式會將大型工作分成較小的工作。 小型工作更容易進行偵錯,而且可以在經過證實後重複使用。 函式可用來隱藏其他函式的詳細資料,讓程式更容易遵循函式。

HLSL 函式有數種方式類似于 C 函式:它們都包含定義和函式主體,同時宣告傳回型別和引數清單。 如同 C 函式,HLSL 驗證會在著色器編譯期間,對引數、引數類型和傳回值進行型別檢查。

不同于 C 函式,HLSL 進入點函式會使用語意將函式引數系結至著色器輸入和輸出, (稱為 HLSL 函式的內部忽略語意) 。 這可讓您更輕鬆地將緩衝區資料系結至著色器,並將著色器輸出系結至著色器輸入。

函式包含宣告和主體,而且宣告必須位於本文之前。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

函式宣告包含大括弧前面的所有專案:

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

函式宣告包含:

  • 傳回類型
  • 函式名稱
  • 選擇性) (引數清單
  • 輸出語意 (選擇性)
  • 注釋 (選擇性)

傳回類型可以是任何 HLSL 基本資料類型,例如 float4:

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
   ...
}

傳回型別可以是已經定義的結構:

struct VS_OUTPUT
{
    float4  vPosition        : POSITION;
    float4  vDiffuse         : COLOR;
}; 

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

如果函式未傳回值,void 可以當做傳回型別使用。

void VertexShader_Tutorial_1(float4 inPos : POSITION )
{
   ...
}

傳回型別一律會先出現在函式宣告中。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION

引數清單會將輸入引數宣告為函式。 它也可以宣告將傳回的值。 有些引數同時是輸入和輸出引數。 以下是採用四個輸入引數的著色器範例。

float4 Light(float3 LightDir : TEXCOORD1, 
             uniform float4 LightColor,  
             float2 texcrd : TEXCOORD0, 
             uniform sampler samp) : COLOR 
{
    float3 Normal = tex2D(samp,texcrd);

    return dot((Normal*2 - 1), LightDir)*LightColor;
}

此函式會傳回最終色彩,這是紋理樣本和淺色的混合。 函式接受四個輸入。 兩個輸入具有語意:LightDir 具有 TEXCOORD1 語意,而 texcrd 具有 TEXCOORD0 語意。 語意表示這些變數的資料會來自頂點緩衝區。 即使 LightDir 變數具有 TEXCOORD1 語意,參數可能不是紋理座標。 TEXCOORDn 語意類型通常用來為未預先定義的類型提供語意, (光線方向沒有頂點著色器輸入語意) 。

其他兩個輸入 LightColor 和 samp 會以 uniform 關鍵字標示。 這些是不會在繪製呼叫之間變更的統一常數。 這些參數的值來自著色器全域變數。

引數可以標示為 in 關鍵字的輸入,並使用 out 關鍵字輸出引數。 引數無法以傳址方式傳遞;不過,如果引數是以 inout 關鍵字宣告,則引數可以是輸入和輸出。 傳遞至以 inout 關鍵字標記之函式的引數會被視為原始的複本,直到函式傳回,並複製回。 以下是使用 inout 的範例:

void Increment_ByVal(inout float A, inout float B) 
{ 
    A++; B++;
}

此函式會遞增 A 和 B 中的值,並傳回它們。

函式主體是函式宣告之後的所有程式碼。

float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
    return mul(inPos, WorldViewProj );
};

本文是由大括弧括住的語句所組成。 函式主體會使用變數、常值、運算式和語句來實作所有功能。

著色器主體會執行兩件事:它會執行矩陣乘法,並傳回 float4 結果。 矩陣乘法是使用 mul (DirectX HLSL) 函式來完成,它會執行 4x4 矩陣乘法。 mul (DirectX HLSL) 稱為內建函式,因為它已經內建在函式的 HLSL 程式庫中。 下一節將更詳細地討論內建函式。

矩陣乘積結合輸入向量 Pos 和複合矩陣 WorldViewProj。 結果是將資料轉換成螢幕空間。 這是我們可以執行的最小頂點著色器處理。 如果我們使用的是固定函式管線,而不是頂點著色器,在進行這項轉換之後,可能會繪製頂點資料。

函式主體中的最後一個語句是 return 語句。 就像 C 一樣,此語句會將 控制項從 函式傳回至呼叫函式的 語句。

函式傳回型別可以是 HLSL 中定義的任何單一資料型別,包括 bool、int half、float 和 double。 傳回型別可以是其中一個複雜的資料類型,例如向量和矩陣。 參考物件的 HLSL 類型不能當做傳回型別使用。 這包括 pixelhader、頂點陰影器、紋理和取樣器。

以下是針對傳回型別使用 結構的函式範例。

float4x4 WorldViewProj : WORLDVIEWPROJ;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VS_HLL_Example(float4 inPos : POSITION )
{
    VS_OUTPUT Out;

    Out.Pos = mul(inPos,  WorldViewProj );

    return Out;
};

float4 傳回類型已取代為結構VS_OUTPUT,現在包含單一 float4 成員。

return 語句會發出函式結尾的訊號。 這是最簡單的 return 語句。 它會將 控制項從 函式傳回給呼叫程式。 它不會傳回任何值。

void main()
{
    return ;
}

return 語句可以傳回一或多個值。 此範例會傳回常值:

float main( float input : COLOR0) : COLOR0
{
    return 0;
}

此範例會傳回運算式的純量結果:

return  light.enabled;

此範例會傳回從區域變數和常值建構的 float4:

return  float4(color.rgb, 1) ;

此範例會傳回 float4,其建構自內建函式傳回的結果,以及一些常值:

float4 func(float2 a: POSITION): COLOR
{
    return float4(sin(length(a) * 100.0) * 0.5 + 0.5, sin(a.y * 50.0), 0, 1);
}

此範例會傳回包含一或多個成員的結構:

float4x4 WorldViewProj;

struct VS_OUTPUT
{
    float4 Pos  : POSITION;
};

VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
    VS_OUTPUT out;
    out.Pos = mul(inPos, WorldViewProj );
    return out;
};

流量控制

大部分的頂點和圖元著色器硬體都是設計來逐行執行著色器線條,並執行每個指令一次。 HLSL 支援流程式控制制,其中包括靜態分支、述詞指令、靜態迴圈、動態分支和動態迴圈。

先前,使用 if 語句會導致元件語言著色器程式碼同時實作 if 端和程式碼流程的另一端。 以下是針對 vs_1_1 編譯的 HLSL 程式碼範例:

if (Value > 0)
    oPos = Value1; 
else
    oPos = Value2; 

以下是產生的元件程式碼:

// Calculate linear interpolation value in r0.w
mov r1.w, c2.x               
slt r0.w, c3.x, r1.w         
// Linear interpolation between value1 and value2
mov r7, -c1                      
add r2, r7, c0                   
mad oPos, r0.w, r2, c1  

某些硬體允許靜態或動態迴圈,但大部分都需要線性執行。 在不支援迴圈的模型中,所有迴圈都必須取消註冊。 例如 ,DepthOfField 範例範例 會針對ps_1_1著色器使用未註冊的迴圈。

HLSL 現在包含這些類型流程式控制制的支援:

  • 靜態分支
  • 述詞指示
  • 靜態迴圈
  • 動態分支
  • 動態迴圈

靜態分支允許根據布林著色器常數開啟或關閉著色器程式碼區塊。 這是根據目前正在轉譯的物件類型來啟用或停用程式碼路徑的便利方法。 在繪製呼叫之間,您可以決定要支援目前著色器的功能,然後設定取得該行為所需的布林值旗標。 在著色器執行期間,會略過布林常數停用的任何語句。

最熟悉的分支支援是動態分支。 使用動態分支時,比較準則會位於變數中,這表示比較是在執行時間 (針對每個頂點或每個圖元進行,而不是在編譯時期發生的比較,或在兩個繪製呼叫之間) 。 效能達到是分支的成本,加上分支端指示的成本。 動態分支是在著色器模型 3 或更新版本中實作。 優化使用這些模型的著色器,類似于優化在 CPU 上執行的程式碼。

HLSL 的程式設計指南