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 から始まるセット) を使用してアクセスすることもできます。 各インデックスは角かっこ内にあります。 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
ベクトルと同様に、複数の行列コンポーネントの読み取りはスウィズリングと呼ばれます。 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;
これは 4 つの乗算であり、各結果は v の個別のコンポーネントに格納されます。 これは 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 つのスカラー値を返します。
関連トピック