成分ごとの演算 (DirectX HLSL)
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 型を使って同じ内容を宣言できます。
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 };
vector 型では、山型かっこを使って成分の型と数を指定します。
ベクトルは最大 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;
行列型
行列は、データの行および列を含むデータ構造体です。データには、任意のスカラー データ型を使用できますが、行列の各要素は同じデータ型である必要があります。行数および列数は、データ型に続く "行 x 列" 文字列で指定します。
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 型を使って同じ内容を宣言できます。
matrix <float, 2, 2> fMatrix = { 0.0f, 0.1, // row 1
2.1f, 2.2f // row 2
};
matrix 型では、山型かっこを使って、型、行数、および列数を指定します。この例は、2 行 x 2 列の浮動小数点行列を作成します。任意のスカラー データ型を使用できます。
次の宣言は、2 行 x 3 列から成る float 値 (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" を記述します。次に、行列にそれぞれの命名セットを使用してアクセスする例を示します。
// 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
配列にアクセスする際に構造体operator "." が使用されていない点に注目してください。配列アクセス表記では、スィズルを使って複数の成分を読み出すことはできません。
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;
行列の順序
均一パラメーターの行列パッキング順序は、デフォルトで列優先に設定されます。これは、行列の各列が単一の定数レジスタに格納されることを意味します。一方、行優先の行列では、行列の各行が単一の定数レジスタに格納されます。行列パッキングは、#pragmapack_matrix ディレクティブを使うか、または row_major キーワードか col_major キーワードを使って変更できます。
行列内のデータは、シェーダーの実行前にシェーダー定数レジスタ内に読み込みます。行列データの読み込み方法には、行優先順と列優先順の 2 通りあります。列優先順とは、行列の各列を単一の定数レジスタに格納することです。一方、行優先順とは、行列の各行を単一の定数レジスタに格納することです。行列に定数レジスタをいくつ使用するかは、重要な検討事項の 1 つです。
行優先順の行列は次のようにレイアウトされます。
11 | 12 | 13 | 14 |
21 | 22 | 23 | 24 |
31 | 32 | 33 | 34 |
41 | 42 | 43 | 44 |
列優先順の行列は次のようにレイアウトされます。
11 | 21 | 31 | 41 |
12 | 22 | 32 | 42 |
13 | 23 | 33 | 43 |
14 | 24 | 34 | 44 |
行優先および列優先の行列順序は、行列の成分がシェーダー入力から読み込まれる順序を決定します。データが定数レジスタに書きこまれた後は、行列の順序がシェーダー コード内からのデータの使用やアクセスの方法に作用することはありません。また、シェーダーの本体に宣言された行列は定数レジスタに格納されません。行優先および列優先のパッキング順は、コンストラクターのパッキング順に影響を与えません。コンストラクターのパッキング順は、常に行優先になります。
行列内のデータの順序はコンパイル時に宣言できます。宣言しない場合には、実行時にコンパイラが、最も効率的に使用できる順序でデータを並べ替えます。
例
HLSL では、2D および 3D のグラフィックスをより簡単にプログラムできるように、ベクトル型および行列型の 2 種類の特別な型を使用します。各型は、1 つ以上の成分を含みます。ベクトルには最大 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*a.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;
結果として、2 つの行列の成分ごとの乗算が行われます (標準の 3 x 3 行列乗算ではありません)。成分ごとの行列乗算で得られる最初の項は、次のとおりです。
mat3.m00 = mat1.m00 * mat2._m00;
これは、得られる最初の項が次のようになる 3 x 3 行列乗算とは異なります。
// 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;
乗算組み込み関数のオーバーロード バージョンは、1 つのオペランドがベクトルでもう 1 つのオペランドが行列である場合に使用します。たとえば、ベクトル * ベクトル、ベクトル * 行列、行列 * ベクトル、行列 * 行列などです。次に例を示します。
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 つのスカラー値を返します。