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 つ以上のベクター コンポーネントを指定することは swizzling と呼ばれます。 次に例を示します。
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 から始まるインデックスのセットである配列アクセス表記を使用してアクセスすることもできます。 各インデックスは角かっこの内側にあります。 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
構造体演算子 "." は配列へのアクセスには使用されないことに注意してください。 配列アクセス表記では、複数のコンポーネントを読み取るために 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
ベクトルと同様に、複数の行列コンポーネントの読み取りは swizzling と呼ばれます。 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キーワード (keyword)で変更できます。
マトリックス内のデータは、シェーダーを実行する前にシェーダー定数レジスタに読み込まれます。 マトリックス データの読み取り方法には、行メジャー順または列メジャー順の 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;
結果は、(標準の 3x3 行列乗算ではなく) 2 つの行列の成分ごとの乗算です。 コンポーネントごとの行列乗算では、この最初の項が生成されます。
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 つのスカラー値を返します。
関連トピック