Per-Component算術演算

HLSL を使用すると、アルゴリズム レベルでシェーダーをプログラムできます。 言語を理解するには、変数と関数を宣言する方法、組み込み関数を使用する方法、カスタム データ型を定義する方法、セマンティクスを使用してシェーダー引数を他のシェーダーやパイプラインに接続する方法を知る必要があります。

HLSL でシェーダーを作成する方法を学習したら、特定のハードウェアのシェーダーをコンパイルし、シェーダー定数を初期化し、必要に応じて他のパイプライン状態を初期化できるように、API 呼び出しについて学習する必要があります。

ベクター型

ベクトルは、1 から 4 つのコンポーネントの間で格納されるデータ構造です。

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 };

ベクター型は山かっこを使用して、コンポーネントの種類と数を指定します。

ベクターには最大 4 つのコンポーネントが含まれており、それぞれに 2 つの名前付けセットのいずれかを使用してアクセスできます。

  • 位置セット: x、y、z、w
  • カラー セット: r、g、b、a

これらのステートメントはどちらも、3 番目のコンポーネントの値を返します。

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

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

名前付けセットは 1 つ以上のコンポーネントを使用できますが、混在することはできません。

// 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.

コンポーネントの読み取り時に 1 つ以上のベクター コンポーネントを指定することは、スウィズリングと呼ばれます。 次に例を示します。

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 

ベクトルにスカラーとしてアクセスすると、ベクターの最初のコンポーネントにアクセスします。 次の 2 つのステートメントは同等です。

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
                               };

マトリックス型では、山かっこを使用して、型、行数、列数を指定します。 次の使用例は、2 つの行と 2 つの列を含む浮動小数点行列を作成します。 任意のスカラー データ型を使用できます。

この宣言は、2 つの行と 3 つの列を持つ浮動小数点値 (32 ビット浮動小数点数) の行列を定義します。

matrix <float, 2, 3> fFloatMatrix;

マトリックスには、行と列で編成された値が含まれています。これは、構造体演算子 "." を使用してアクセスでき、その後に 2 つの名前付けセットのいずれかが続きます。

  • 0 から始まる行列の位置:
    • _m00、_m01、_m02、_m03
    • _m10、_m11、_m12、_m13
    • _m20、_m21、_m22、_m23
    • _m30、_m31、_m32、_m33
  • 1 から始まる行列の位置:
    • _11, _12, _13, _14
    • _21, _22, _23, _24
    • _31, _32, _33, _34
    • _41, _42, _43, _44

各名前付けセットは、アンダースコアの後に行番号と列番号が続きます。 0 から始まる規則には、行番号と列番号の前に文字 "m" も含まれます。 2 つの名前付けセットを使用してマトリックスにアクセスする例を次に示します。

// 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

ベクトルと同様に、名前付けセットでは、いずれかの名前付けセットから 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

行列には、配列アクセス表記を使用してアクセスすることもできます。これは、インデックスの 0 から始まるセットです。 各インデックスは角かっこの内側にあります。 次のインデックスを使用して、4 x 4 のマトリックスにアクセスします。

  • [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

構造体演算子 "." は配列へのアクセスには使用されないことに注意してください。 配列アクセス表記法では、複数のコンポーネントを読み取るために swizzling を使用することはできません。

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

ただし、配列へのアクセスでは、マルチコンポーネント ベクターを読み取ることができます。

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

ベクトルと同様に、複数の行列コンポーネントの読み取りはスウィズリングと呼ばれます。 複数のコンポーネントを割り当てることができます。1 つの名前空間のみが使用されていると仮定します。 これらはすべて有効な割り当てです。

// 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; 

行列の順序付け

一様パラメーターのマトリックスパッキング順序は、既定で列メジャーに設定されます。 これは、マトリックスの各列が 1 つの定数レジスタに格納されていることを意味します。 一方、行メジャー マトリックスは、マトリックスの各行を 1 つの定数レジスタにパックします。 マトリックスパッキングは、 #pragmapack_matrix ディレクティブ、または row_major または column_major キーワードを使用して変更できます。

マトリックス内のデータは、シェーダーの実行前にシェーダー定数レジスタに読み込まれます。 マトリックス データの読み取り方法には、行と主の順序と列メジャーの順序の 2 つの選択肢があります。 列の主な順序は、各マトリックス列が 1 つの定数レジスタに格納されることを意味し、行メジャーの順序は、マトリックスの各行が 1 つの定数レジスタに格納されることを意味します。 これは、マトリックスに使用される定数レジスタの数に関する重要な考慮事項です。

行メジャー マトリックスは、次のようにレイアウトされます。

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 グラフィックスのプログラミングを容易にするために、ベクター型とマトリックス型の 2 つの特殊な型を使用します。 これらの各型には、複数のコンポーネントが含まれています。ベクトルには最大 4 つのコンポーネントが含まれており、マトリックスには最大 16 個のコンポーネントが含まれます。 標準的な HLSL 方程式でベクトルとマトリックスを使用する場合、計算はコンポーネントごとに機能するように設計されています。 たとえば、HLSL は次の乗算を実装します。

float4 v = a*b;

を 4 要素乗算として指定します。 結果は 4 つのスカラーです。

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の別々のコンポーネントに格納される4つの乗算です。 これは、4 要素乗算と呼ばれます。 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;

結果は、2 つの行列のコンポーネントごとの乗算です (標準の 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;

乗算組み込み関数のオーバーロードされたバージョンでは、一方のオペランドがベクトルで、もう一方のオペランドが行列であるケースが処理されます。 例: vector * vector、vector * matrix、matrix * vector、matrix * matrix。 次に例を示します。

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) キャストを使用して pos ベクターを列ベクトルにキャストします。 キャストによってベクトルを変更するか、乗算するために指定された引数の順序をスワップすることは、行列の入れ替えと同じです。

自動キャスト変換により、乗算関数とドット組み込み関数は、ここで使用したのと同じ結果を返します。

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

乗算のこの結果は、1x4 * 4x1 = 1x1 ベクトルです。 これはドット積と同じです。

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

1 つのスカラー値を返します。

データ型 (DirectX HLSL)