Per-Component數學運算

使用 HLSL,您可以在演算法層級對著色器進行程式設計。 若要瞭解語言,您必須知道如何宣告變數和函式、使用內建函式、定義自訂資料類型,以及使用語意將著色器引數連接到其他著色器和管線。

一旦您瞭解如何在 HLSL 中撰寫著色器,您必須瞭解 API 呼叫,以便您可以:編譯特定硬體的著色器、初始化著色器常數,並視需要初始化其他管線狀態。

向量類型

向量是包含介於一到四個元件之間的資料結構。

bool    bVector;   // scalar containing 1 Boolean
bool1   bVector;   // vector containing 1 Boolean
int1    iVector;   // vector containing 1 int
float3  fVector;   // vector containing 3 floats
double4 dVector;   // vector containing 4 doubles

緊接在資料類型後面的整數是向量上的元件數目。

初始化運算式也可以包含在宣告中。

bool    bVector = false;
int1    iVector = 1;
float3  fVector = { 0.2f, 0.3f, 0.4f };
double4 dVector = { 0.2, 0.3, 0.4, 0.5 };

或者,向量類型可用來建立相同的宣告:

vector <bool,   1> bVector = false;
vector <int,    1> iVector = 1;
vector <float,  3> fVector = { 0.2f, 0.3f, 0.4f };
vector <double, 4> dVector = { 0.2, 0.3, 0.4, 0.5 };

向量類型會使用角括弧來指定元件的類型和數目。

向量最多包含四個元件,每個元件都可以使用兩個命名集的其中一個來存取:

  • 位置集:x,y,z,w
  • 色彩集:r、g、b、a

這些語句都會傳回第三個元件中的值。

// Given
float4 pos = float4(0,0,2,1);

pos.z    // value is 2
pos.b    // value is 2

命名集可以使用一或多個元件,但不能混合。

// Given
float4 pos = float4(0,0,2,1);
float2 temp;

temp = pos.xy  // valid
temp = pos.rg  // valid

temp = pos.xg  // NOT VALID because the position and color sets were used.

讀取元件時指定一或多個向量元件,稱為「雜亂」。 例如:

float4 pos = float4(0,0,2,1);
float2 f_2D;
f_2D = pos.xy;   // read two components 
f_2D = pos.xz;   // read components in any order       
f_2D = pos.zx;

f_2D = pos.xx;   // components can be read more than once
f_2D = pos.yy;

遮罩可控制寫入的元件數目。

float4 pos = float4(0,0,2,1);
float4 f_4D;
f_4D    = pos;     // write four components          

f_4D.xz = pos.xz;  // write two components        
f_4D.zx = pos.xz;  // change the write order

f_4D.xzyw = pos.w; // write one component to more than one component
f_4D.wzyx = pos;

指派無法多次寫入相同的元件。 因此,此語句的左側無效:

f_4D.xx = pos.xy;   // cannot write to the same destination components 

此外,元件名稱空格不能混合。 這是不正確元件寫入:

f_4D.xg = pos.rgrg;    // invalid write: cannot mix component name spaces 

以純量的形式存取向量將會存取向量的第一個元件。 下列兩個陳述式是相等的。

f_4D.a = pos * 5.0f;
f_4D.a = pos.r * 5.0f;

矩陣類型

矩陣是包含資料列和資料行的資料結構。 不過,資料可以是任何純量資料類型,不過,矩陣的每個元素都是相同的資料類型。 資料列和資料行數目是以附加至資料類型的資料列逐欄字串來指定。

int1x1    iMatrix;   // integer matrix with 1 row,  1 column
int2x1    iMatrix;   // integer matrix with 2 rows, 1 column
...
int4x1    iMatrix;   // integer matrix with 4 rows, 1 column
...
int1x4    iMatrix;   // integer matrix with 1 row, 4 columns
double1x1 dMatrix;   // double matrix with 1 row,  1 column
double2x2 dMatrix;   // double matrix with 2 rows, 2 columns
double3x3 dMatrix;   // double matrix with 3 rows, 3 columns
double4x4 dMatrix;   // double matrix with 4 rows, 4 columns

資料列或資料行的最大數目為 4;最小值為 1。

在宣告矩陣時,可以初始化矩陣:

float2x2 fMatrix = { 0.0f, 0.1, // row 1
                     2.1f, 2.2f // row 2
                   };   

或者,矩陣類型可用來建立相同的宣告:

matrix <float, 2, 2> fMatrix = { 0.0f, 0.1, // row 1
                                 2.1f, 2.2f // row 2
                               };

矩陣類型會使用角括弧來指定類型、資料列數目和資料行數目。 此範例會建立浮點矩陣,其中包含兩個數據列和兩個數據行。 您可以使用任何純量資料類型。

此宣告定義浮點數矩陣, (32 位浮點數) 兩個數據列和三個數據行:

matrix <float, 2, 3> fFloatMatrix;

矩陣包含以資料列和資料行組織的值,可以使用結構運算子 「.」 來存取,後面接著兩個命名集的其中一個:

  • 以零起始的資料列資料行位置:
    • _m00、_m01、_m02、_m03
    • _m10、_m11、_m12、_m13
    • _m20、_m21、_m22_m23
    • _m30、_m31、_m32、_m33
  • 以單一為基礎的資料列資料行位置:
    • _11, _12, _13, _14
    • _21, _22, _23, _24
    • _31, _32, _33, _34
    • _41, _42, _43, _44

每個命名集的開頭都是底線,後面接著資料列編號和資料行編號。 以零起始的慣例也包含資料列和資料行編號之前的字母 「m」。 以下是使用兩個命名集來存取矩陣的範例:

// given
float2x2 fMatrix = { 1.0f, 1.1f, // row 1
                     2.0f, 2.1f  // row 2
                   }; 

float f_1D;
f_1D = matrix._m00; // read the value in row 1, column 1: 1.0
f_1D = matrix._m11; // read the value in row 2, column 2: 2.1

f_1D = matrix._11;  // read the value in row 1, column 1: 1.0
f_1D = matrix._22;  // read the value in row 2, column 2: 2.1

就像向量一樣,命名集可以使用任一命名集中的一或多個元件。

// Given
float2x2 fMatrix = { 1.0f, 1.1f, // row 1
                     2.0f, 2.1f  // row 2
                   };
float2 temp;

temp = fMatrix._m00_m11 // valid
temp = fMatrix._m11_m00 // valid
temp = fMatrix._11_22   // valid
temp = fMatrix._22_11   // valid

您也可以使用陣列存取標記法來存取矩陣,這是以零起始的索引集。 每個索引都位於方括弧內。 使用下列索引存取 4x4 矩陣:

  • [0][0], [0][1], [0][2], [0][3]
  • [1][0], [1][1], [1][2], [1][3]
  • [2][0], [2][1], [2][2], [2][3]
  • [3][0], [3][1], [3][2], [3][3]

以下是存取矩陣的範例:

float2x2 fMatrix = { 1.0f, 1.1f, // row 1
                     2.0f, 2.1f  // row 2
                   };
float temp;

temp = fMatrix[0][0] // single component read
temp = fMatrix[0][1] // single component read

請注意,結構運算子 「.」 不會用來存取陣列。 陣列存取標記法無法使用撥動來讀取多個元件。

float2 temp;
temp = fMatrix[0][0]_[0][1] // invalid, cannot read two components

不過,陣列存取可以讀取多元件向量。

float2 temp;
float2x2 fMatrix;
temp = fMatrix[0] // read the first row

如同向量,讀取一個以上的矩陣元件稱為撥動。 您可以指派多個元件,但假設只使用一個名稱空間。 這些是所有有效的指派:

// Given these variables
float4x4 worldMatrix = float4( {0,0,0,0}, {1,1,1,1}, {2,2,2,2}, {3,3,3,3} );
float4x4 tempMatrix;

tempMatrix._m00_m11 = worldMatrix._m00_m11; // multiple components
tempMatrix._m00_m11 = worldMatrix.m13_m23;

tempMatrix._11_22_33 = worldMatrix._11_22_33; // any order on swizzles
tempMatrix._11_22_33 = worldMatrix._24_23_22;

遮罩可控制寫入的元件數目。

// Given
float4x4 worldMatrix = float4( {0,0,0,0}, {1,1,1,1}, {2,2,2,2}, {3,3,3,3} );
float4x4 tempMatrix;

tempMatrix._m00_m11 = worldMatrix._m00_m11; // write two components
tempMatrix._m23_m00 = worldMatrix._m00_m11;

指派無法多次寫入相同的元件。 因此,此語句的左側無效:

// cannot write to the same component more than once
tempMatrix._m00_m00 = worldMatrix._m00_m11;

此外,元件名稱空格不能混合。 這是不正確元件寫入:

// Invalid use of same component on left side
tempMatrix._11_m23 = worldMatrix._11_22; 

矩陣排序

根據預設,統一參數的矩陣封裝順序會設定為數據行主要。 這表示矩陣的每個資料行都會儲存在單一常數暫存器中。 另一方面,資料列主要矩陣會將矩陣的每個資料列封裝在單一常數暫存器中。 矩陣封裝可以使用 #pragmapack_matrix 指示詞或 row_majorcolumn_major 關鍵字來變更。

矩陣中的資料會在著色器執行之前載入著色器常數暫存器中。 矩陣資料的讀取方式有兩種選擇:以資料列主要順序或資料行主要順序表示。 資料行主要順序表示每個矩陣資料行都會以單一常數暫存器儲存,而資料列主要順序表示矩陣的每個資料列都會儲存在單一常數暫存器中。 這是矩陣使用多少常數暫存器的重要考慮。

資料列主要矩陣的配置如下:

11
21
31
41

12
22
32
42

13
23
33
43

14
24
34
44

 

資料行主要矩陣的配置如下:

11
12
13
14

21
22
23
24

31
32
33
34

41
42
43
44

 

資料列主要和資料行主要矩陣順序會決定矩陣元件從著色器輸入讀取的順序。 將資料寫入常數暫存器之後,矩陣順序就不會影響在著色器程式碼內使用或存取資料的方式。 此外,在著色器主體中宣告的矩陣不會封裝成常數暫存器。 資料列主要和資料行主要封裝順序不會影響建構函式的封裝順序, (一律遵循資料列主要排序) 。

矩陣中的資料順序可以在編譯時期宣告,或者編譯器會在執行時間排序資料,以取得最有效率的使用。

範例

HLSL 使用兩種特殊類型:向量類型和矩陣類型,讓程式設計 2D 和 3D 圖形更容易。 每個類型都包含一個以上的元件;向量最多包含四個元件,而矩陣最多包含 16 個元件。 當向量和矩陣用於標準 HLSL 方程式時,所執行的數學是設計來針對每個元件運作。 例如,HLSL 會實作此乘法:

float4 v = a*b;

乘以四個元件。 結果為四個純量:

float4 v = a*b;

v.x = a.x*b.x;
v.y = a.y*b.y;
v.z = a.z*b.z;
v.w = a.w*b.w;

這是四個乘法,其中每個結果會儲存在 v 的個別元件中。 這稱為四個元件乘積。 HLSL 使用元件數學,讓撰寫著色器更有效率。

這與乘法非常不同,通常實作為產生單一純量的點乘積:

v = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;

矩陣也會在 HLSL 中使用每個元件作業:

float3x3 mat1,mat2;
...
float3x3 mat3 = mat1*mat2;

結果是兩個矩陣的個別元件乘以 (,而不是標準 3x3 矩陣乘以) 。 每個元件矩陣相乘會產生這個第一個詞彙:

mat3.m00 = mat1.m00 * mat2._m00;

這與 3x3 矩陣相乘不同,這會產生第一個詞彙:

// First component of a four-component matrix multiply
mat.m00 = mat1._m00 * mat2._m00 + 
          mat1._m01 * mat2._m10 + 
          mat1._m02 * mat2._m20 + 
          mat1._m03 * mat2._m30;

乘積內建函式的多載版本會處理一個運算元是向量,另一個運算元是矩陣。 例如:向量 * 向量、向量 * 矩陣、矩陣 * 向量和矩陣 * 矩陣。 例如:

float4x3 World;

float4 main(float4 pos : SV_POSITION) : SV_POSITION
{
    float4 val;
    val.xyz = mul(pos,World);
    val.w = 0;

    return val;
}   

會產生與下列相同的結果:

float4x3 World;

float4 main(float4 pos : SV_POSITION) : SV_POSITION
{
    float4 val;
    val.xyz = (float3) mul((float1x4)pos,World);
    val.w = 0;

    return val;
}   

本範例會使用 float1x4 () cast,將 pos 向量轉換成資料行向量。 藉由轉換來變更向量,或交換提供給乘積的引數順序,相當於轉置矩陣。

自動轉換會導致乘積和點內建函式傳回與這裡所使用的相同結果:

{
  float4 val;
  return mul(val,val);
}

乘法的這個結果是 1x4 * 4x1 = 1x1 向量。 這相當於點乘積:

{
  float4 val;
  return dot(val,val);
}

會傳回單一純量值。

(DirectX HLSL)