DirectX 8.0 のプログラマブル シェーダ
Philip Taylor
Microsoft Corporation
January 15, 2001
「Driving DirectX」 にようこそ。先月は DirectX 8.0 グラフィックスの簡略化について、その概要を紹介しました。今月は、プログラマブル パイプラインの新機能についてお話ししましょう。追加されたプログラマブル パイプラインは、DirectX 8.0 グラフィックスの豊富な新機能の中でも最も重要な最新機能であることは間違いありません。
プログラマビリティ
Direct3D アーキテクチャには 2 つのプログラマブル セクションがあります。それは、頂点シェーダとピクセル シェーダです。頂点シェーダは、頂点アセンブリの前に呼び出され、頂点を操作します。ピクセル シェーダは DrawPrimitive コールや DrawIndexedPrimitive コールの後に呼び出され、レンダリング ターゲットに書き込むピクセルを生成します。頂点とピクセルのオペレーションにプログラマブル シェーダを追加することで、ムービー品質に匹敵するリアルタイム プログラマブル エフェクトのフレームワークが得られます。ゲーム開発者にとっては、プログラマブル パイプラインで対応できるエフェクトならどれでも実装できるというこのプログラマビリティの革新的な自由度は、すばらしいゲームの新しいラウンドの扉を開くことのできる可能性を持っています。たとえば、次のような主要機能がサポートされています。
基本ジオメトリ トランスフォーム
簡単なライティング モデル
キャラクタ アニメーションのスキニング
キャラクタ アニメーションのトウィーニング
テクスチャ トランスフォーム
テクスチャ生成
環境マッピング
プログラマブル パイプラインを提供する目的は、開発者を束縛から解放することだけではありません。グラフィックス パイプライン用のプログラマブル モデルでは、汎用性の高い構文で共通のオペレーションを指定できます。プログラマブルな機能に対する固定機能として、API では、モードやフラグなど、増加の一途をたどる数多くのオペレーションを定義しなければなりません。さらに、強力になるハードウェアのパワーにより、より多くのカラー、テクスチャ、頂点など、データ入力によって倍増するオペレーションのためのトークン スペースは、API やドライバに関してはますます複雑になりつつあります。一方、プログラマブル モデルでは、しかるべきカラーとしかるべきテクスチャをライティング モデルのしかるべき場所に配置するという、効率的で簡単なオペレーションが可能になります。考えられるモードのすべてを検索する必要はありません。マシン アーキテクチャを学べば、あとは必要なアルゴリズムを指定するだけです。
プログラマブル パイプラインは、スケーラビリティと進化能力をもたらします。ハードウェアの機能は急速に発展し続けており、プログラム的表現方法は、そのスケーラビリティにより API の適応を助けます。新しい機能や能力は、次の方法で簡単に追加公開していくことができます。
新しい命令の追加
新しいデータ入力の追加
新しいプログラム リソース制限の追加。利用できるレジスタの数、プログラムのサイズなど
パイプライン トークン スペースの問題など、月日の経過とともにますます複雑さを増す問題においては、言語とコードが最もスケーラビリティに富むツールであるとこれまで考えられてきました。また、シェーダに新機能が追加されるたびに、Direct3D 内の小さなコードは変更を余儀なくされてきました。さらに、ソフトウェア開発者にとって、ハードウェアよりもプログラミングの方が理解しやすく、その意味でプログラマブル モデルは扱いやすいといえます。ハードウェアの機能をコード パラダイムに割り当てた API こそ、ソフトウェア開発者の要求に本当に応えることのできる API だといえるでしょう。
図1. DirectX 8.0 のプログラマブル パイプライン アーキテクチャ
頂点シェーダ
プログラマブル頂点シェーダは、Microsoft Direct3D ジオメトリ パイプラインの「固定機能」トランスフォームとライティング モジュールを置き換えます。頂点シェーダが行う頂点処理は、1 つの頂点に 1 回で適用できるオペレーションだけで構成されています。頂点パイプラインにおけるオペレーション シーケンスを次に示します。
頂点アセンブリ (例:フォーマット変換)
テッセレーション
頂点シェーダ (固定形式 TnL ステージの代わり)
プリミティブ アセンブリ
クリッピング、視錐台および任意
背面カリング
パースペクティブ除算
ビューポート変換
頂点処理ステップの出力は、個別に、ラスタ化可能な頂点として定義され、どれもクリップ空間での位置座標 (x、y、z、w)、カラー、テクスチャ座標、フォグ濃度、ポイントサイズ情報からなります。その後の処理段階では、これらの位置がビューポートにマップされ、複数の頂点がプリミティブに構成されてから、プリミティブがクリップされます。各頂点シェーダには、入力データ ストリームにおいてシェーダが頂点要素の使用法などシェーダへの入力を定義する宣言と、各頂点に適用されるオペレーションを定義する関数が組み込まれます。
頂点シェーダの宣言部は、シェーダの静的な外部インターフェイスを定義します。頂点シェーダ宣言によって、入力データは頂点シェーダの入力レジスタと結合します。DirectX 8.0 グラフィックスでは、シェーダで利用できるよう、ストリームの概念にもとづいて入力レジスタにデータを結合しました。ストリームは、コンポーネント データの均一な配列であり、各コンポーネントは、位置、法線、カラーなどの単一エンティティを表す 1 つ以上の要素からなるコンポーネントデータの均一な配列です。ストリームでは、複数の頂点バッファからグラフィックス チップによる DMA を並列に実行でき、アプリケーション データから自然にマッピングできます。これにより、些細なマルチテクスチャ対マルチパスも可能になります。概念的には、次のように考えられます。
頂点は n ストリームからなる。
ストリームは m 要素からなる。
要素は、[位置、カラー、法線、テクスチャ座標] である。
IDirect3DDevice8::SetStreamSource IDirect3DDevice8::SetStreamSource メソッドは頂点バッファをデータ ストリームに結合し、プリミティブ処理関数を供給する複数のデータ ストリーム ポートの 1 つに頂点データを関連付けます。ストリーム データは、IDirect3DDevice8::DrawPrimitive などのドローイング メソッドが呼び出されない限り参照されません。プログラマブル頂点シェーダ用の、頂点入力レジスタに対する入力頂点要素のマッピングは、シェーダ宣言で定義しますが、その利用法に関する個々のセマンティクスは入力頂点要素には組み込まれません。
入力頂点要素の解釈は、シェーダ命令でプログラムします。頂点シェーダ関数は、各頂点に適用するために、一続きの命令で定義します。プログラマブル頂点シェーダ用の頂点出力は、シェーダ関数の命令で明示的に書き込まれます。シェーダ関数は、定義された型なしのベクトル言語で定義します。値はすべて IEEE float [4] で名前を示し、シェーダの前に頂点バッファ形式から変換されます。この言語は 3D グラフィックス用であり、内積などの共通演算の他、たとえば zyx を xyz に再整列させるような、任意の「入れ換え」コンポーネントの能力も備えています。
シェーダ関数は小さいプログラムなので、その実行環境を理解することが大事です。シェーダは、ライトや行列などの定数を保持する定数レジスタ、中間計算を保存するテンポラリ レジスタ、アドレス レジスタ、出力レジスタ、命令配列など、入力レジスタで頂点データをアクセスします。制限について述べると、Version 1.0 頂点シェーダがアクセスできるリソースは、表 1. に示すとおりです。将来的に、サポートするリソース数は増え、さらに強力なハードウェアをサポートするようになるでしょう。
リソース タイプ | 最大利用可能数 |
---|---|
入力レジスタ | 16 |
定数レジスタ | 96 |
テンポラリ レジスタ | 12 |
アドレス レジスタ | 1 |
出力レジスタ | ラスタライザごと |
最大命令カウント | 128 |
表 1. 頂点シェーダ プログラムリソース
ハードウェア実装も近く登場するでしょうが、DirectX 8.0 には、各プロセッサに最適化されたソフトウェア実装が含まれています。頂点シェーダは、1 度に 1 つの頂点を処理するので、エリア計算、カリング、テッセレーションなどの処理はできません。命令の書式は次のとおりです。
op dest, src0,src1,src2
ここで、
op - は命令です。
dest - は宛先レジスタです。
src[0,1,2] - は、使用するソース レジスタであり、その数値は個々の命令によって異なります。
頂点シェーダには、一般命令とマクロ命令という 2 種類の命令を組み込むことができます。また、定数命令とバージョン命令もあります。オプションの入れ換え修飾子やマスキング修飾子についてはここではふれません。それらの詳細については、Direct3D のドキュメントを参照してください。
頂点シェーダ命令セットには分岐オペレーションがなく、シェーダは、基本的に並列化できることを意味します。並列化により、高速実装が可能です。個々の一般命令には、実行時に 1 つの命令スロットとクロック サイクルが必要です。マクロ命令は、一連の一般命令に展開されるので、実行するには、展開する一般命令と同じだけの命令スロットと、命令スロット当たり最低 1 クロックが必要です。
一般命令は、次のとおりです。
命令 | 説明 | クロック |
---|---|---|
add | 加算 | 1 |
dp3 | 3 要素の内積 | 1 |
dp4 | 4 要素の内積 | 1 |
dst | 距離ベクトル | 1 |
expp | 指数 10 ビット精度 | 1 |
lit | ライティング係数 | 1 |
logp | 対数 10 ビット精度 | 1 |
mad | 加算と乗算 | 1 |
max | 最大 | 1 |
min | 最小 | 1 |
mov | 移動 | 1 |
mul | 乗算 | 1 |
rcp | 逆数 | >1 |
rsq | 逆数平方根 | >1 |
sge | 以上 | 1 |
slt | 未満 | 1 |
sub | 減算 | 1 |
表2. 頂点シェーダの一般命令
マクロ命令は、次のとおりです。
命令 | 説明 | スロット/クロック |
---|---|---|
exp | 指数の基数 2 完全精度 | 12 |
frc | 小数部 | 3 |
log | 対数の底 2 完全精度 | 12 |
m3x2 | 3×2 ベクトル行列乗算 | 2 |
m3x3 | 3×3 ベクトル行列乗算 | 3 |
m3x4 | 3×4 ベクトル行列乗算 | 4 |
m4x3 | 4×3 ベクトル行列乗算 | 3 |
m4x4 | 4×4 ベクトル行列乗算 | 4 |
表3. 頂点シェーダ マクロ命令
定数とバージョンの定義文もあります。
def | 定数定義 |
vs | バージョンとタイプ |
表4. 定数命令とバージョン命令
バージョン命令が先であり、定数定義命令はバージョン命令と他のすべての命令の間に配置します。下の図 2 は、頂点シェーダ アーキテクチャのブロック図です。表 1 の制限をもとに、頂点シェーダ プログラムに利用できるリソースのイメージを描いてみましょう。
図2. 頂点シェーダ アーキテクチャ
一定カラーで 1 行列変換を含む頂点シェーダの例を次に示します。
vs.1.0
dp4 r0.x, v0, c[0] ;基本トランスフォーム
dp4 r0.y, v0, c[1]
dp4 r0.z, v0, c[2]
dp4 r0.w, v0, c[3]
mov oD0, c[4] ; 一定カラー シェーディング
mov oPos, r0 ; 出力
このシェーダには、6 つの一般命令があり、6 サイクルで実行されます。
Direct3DDevice8::CreateVertexShader と IDirect3DDevice8::SetVertexShader は、頂点シェーダ用です。固定機能パイプラインのシェーダを作成するときは、NULL 値の pFunction パラメータで IDirect3DDevice8::CreateVertexShader を呼び出します。pFunction が NULL でない場合、シェーダはプログラマブルです。IDirect3DDevice8::SetVertexShader を呼び出すと、現在のアクティブ シェーダが設定されます。これは、パイプラインのレンダリングでプログラマブルな頂点処理と固定機能頂点処理のどちらが使用されるかを定義します。
IDirect3DDevice8::GetVertexShaderConstant および IDirect3DDevice8::SetVertexShaderConstant によって、頂点シェーダ定数レジスタの値を設定し、取り出すことができます。Direct3DX ユーティリティライブラリには、D3DXAssembleShader と D3DXAssembleShaderFromFileA/D3DXAssembleShaderFromFileW が組み込まれており、これでシェーダをコンパイルします。
以上、ざっと頂点シェーダについて説明しました。次はピクセル シェーダです。
ピクセル シェーダ
プログラマブル ピクセル シェーダは、Microsoft Direct3D ピクセル パイプラインのマルチテクスチャに代わる働きをします。ピクセル シェーダが実行するピクセル処理には、単独のピクセルに適用されるオペレーションだけが組み込まれています。ピクセル パイプラインのオペレーション シーケンスは次のとおりです。
三角形セットアップ
ピクセル シェーダ (固定形式マルチ テクスチャに代わる働き)
補間カラー、テクスチャ座標など
テクスチャの取得
テクスチャ/カラー ブレンド
フォグ ブレンド
フレームバッファ ブレンド
ピクセル シェーダは、カラー オペレーションとアルファブレンド オペレーション、およびテクスチャ アドレス指定オペレーションを制御します。ピクセル シェーダが生成する結果は、レジスタ r0 または出力ピクセル カラーの内容です。シェーダが処理を終了したときにどんな内容であれ、その内容はフォグ ステージとレンダー ターゲット ブレンダに転送されてさらに処理を受けます。ピクセル シェーダで利用できるリソースは次のとおりです。
リソース タイプ | 最大利用可能数 |
---|---|
テクスチャ レジスタ | 4 |
テンポラリ レジスタ | 2 |
カラー レジスタ | 2 |
定数レジスタ | 8 |
命令カウント | 8 |
テクスチャ アドレス オペレーション | 4 |
出力レジスタなし | R0 のみ生成 |
表 5. ピクセル シェーダ リソース
ピクセル シェーダ命令にも、次の構文を使用します。
op, dest, src0,src1,src2
ここで、
op - は命令です。
dest - は宛先レジスタです。
src[0,1,2] - は、使用するソース レジスタであり、その数値は個々の命令によって異なります。
ピクセル シェーダには、カラー/アルファ ブレンド命令とテクスチャ アドレス指定命令という 2 種類の命令が組み込まれます。また、先に頂点シェーダで見てきた定数命令とバージョン命令と同じピクセル シェーダもあります。オプション オペレーションや引数修飾子はここでは説明しません。それらの詳細については、Direct3D のドキュメントを参照してください。ピクセル カラー オペレーションとアルファ ブレンド オペレーションでは、カラー データが変更され、テクスチャ アドレス指定オペレーションはテクスチャ座標データが処理されます。
ピクセル シェーダ命令セットにも分岐オペレーションはありません。つまりシェーダは、基本的に並列化できるわけです。並列化により、高速実装が可能です。各命令は、頂点命令と同じく、1 つのクロック サイクルを採用します。ピクセル シェーダを実行するピクセル シェーダには、RGB データ用のベクトル パイプラインとアルファ チャンネル データ用のスカラ パイプラインという 2 つの並列パイプラインがあります。これらの命令を組み合わせれば、両方のパイプラインを並列に使用できます。ピクセル ブレンド (カラーとアルファ) オペレーションを組み合わせれば、カラー命令とアルファ命令を 1 つのスロットで処理できます。これらのパイプラインを並列処理すれば、必要なクロック サイクルの数を節約できて、プロセッサの有効利用ととともにパフォーマンスを改善できます。基本的には、ピクセル フィル レートのパフォーマンスに反映されます。
命令を組み合わせるには、次のように、ペアの 2 番めの命令の前にプラス記号 (+) を置きます。
mul r0.rgb, t0, v0 // コンポーネントにカラーを乗算
+ add r0.a, t0, v0 // しかし、アルファ コンポーネントを加算
内積命令は特別です。ベクトル演算であり、つねにベクトル パイプラインで実行します。そのため、次のピクセル シェーダのサンプル コードのように、スカラ パイプに異なる命令を指定して、ここでも命令を組み合わせることができます。
dp3 r0.rgb, t0, v0
mul r0.a, t0, v0
ベクトル命令としての内積オペレーションなしで、内積オペレーションをスカラ命令として指定することはできません。
テクスチャ宣言 (テクスチャ アドレス指定命令) は、独立したピクセル シェーダ命令のセットであり、カラー命令やアルファ ブレンド命令とは区別します。これらの命令は、ピクセル シェーダの先頭に配置し、定義済みの値にしか使用できません。テクスチャ アドレス指定命令でもオペレーションを実行できます。たとえば、サンプリング ステージでアドレスとして使用されるテクスチャ座標を変更し、摂動エフェクトを実現できます。入力レジスタ カラーとテクスチャ ステージの間では直接マッピングを行います。Direct3DX ピクセル シェーダ アセンブラのテンポラリ レジスタは、対応するテクスチャ ステージからテクスチャ カラーとともにプリロードされます。逆に言えば、テクスチャは IDirect3DDevice8::SetTexture メソッドの呼び出しによって、テンポラリ レジスタに結合されます。さらに、テクスチャ座標は、直接マップされ、テクスチャ宣言で定義されます。テクスチャ アドレスによる摂動エフェクト オペレーションの構文はテクスチャ宣言のかたちでシェーダ プログラムそのものに組み込まれます。これにより、ピクセル シェーダ本体で使用するためのテンポラリ レジスタ ファイルのプリロードの間に、テクスチャからサンプルしたカラー値に対して、アドレス摂動エフェクト オペレーションを適用する方法を制御します。テクスチャ アドレス指定オペレーションの一部を表 6 に示します。各テクスチャ アドレス指定オペレーションは、ピクセル シェーダに利用できる命令リソースを 1 スロット使用します。Microsoft DirectX 8.0 では、テクスチャ アドレス指定命令は、カラー ブレンド命令の前に配置します。
命令 | 説明 |
---|---|
tex | 修正なし |
texbem | バンプ環境マップ |
texbeml | 輝度付きバンプ環境マップ |
texcoord | テクスチャ座標 |
texkill | ピクセルのマスク アウト |
texm3x2pad | 3×2 行列乗算に対する入力 |
texm3x2tex | 3×2 行列乗算の結果 |
texreg2ar | アルファ成分と R 成分の再マッピング |
texreg2gb | G 成分と B 成分の再マッピング |
表6. テクスチャ アドレス指定命令
さらに、ピクセル シェーダ API は、テクスチャからサンプリングしたベクトルに対してピクセル単位 3×3 行列乗算を実行するために、多くのメソッドをサポートしています。3×3 行列は、3 つの連続テクスチャ宣言ステージのテクスチャ座標からなります。これらの行列乗算オペレーションは、複数のテクスチャ アドレス オペレーションで構成されます。
表7 は、ピクセル単位 3×3 行列乗算に関係があるその他のテクスチャ アドレス指定命令です。
命令 | 説明 |
---|---|
texm3x3pad | 3×3 行列乗算に対する入力 |
texm3x3spec | スペキュラ反射と環境マッピング |
texm3x3tex | 3×3 行列乗算の結果 |
texm3x3vspec | 固定視点ベクトルを含まないスペキュラ反射/環境マッピング |
表7. 3x3 テクスチャ アドレス指定命令
カラー命令とアルファ命令が残っています。ピクセル カラーとアルファ ブレンド オペレーションでは、カラー データを修正します。ピクセル シェーダ命令は、すべてピクセル単位で実行されます。したがって、パイプラインの他のピクセルの情報は持っていません。カラー命令とアルファ命令は次のとおりです。
命令 | 説明 |
---|---|
add | 加算 |
cnd | 条件 |
dp3 | 3 要素ベクトルの内積 |
lrp | 線形補間ブレンド |
mad | 乗算と加算 |
mov | コピー元からコピー先へのコピー |
mul | 乗算 |
sub | ソース オペランドの 2 色の差をロードする |
表8. ピクセル シェーダ カラーとアルファ命令
以上で、ピクセル シェーダに利用できるリソースとオペレーションを理解できたことでしょう。これらの内容をふまえて図 3 を見れば、ピクセル シェーダ環境が視覚的に理解できます。
図3. ピクセル シェーダ アーキテクチャ
簡単な拡散照明を持ったピクセル シェーダの例を次に示します。
ps.1.0 ; DirectX8 のバージョン
tex t0 ; n-map の宣言
texm3x3pad t1, t0_bx2 ; 1 行目を変換
texm3x3pad t2, t0_bx2 ; 2 行目を変換
texm3x3tex t3, t0_bx2 ; 3 行目を変換
dp3_sat r0, t3_bx2, v0_bx2
Microsoft DirectX 8.0 では、IDirect3DDevice8::CreatePixelShader メソッドでピクセル シェーダを作成しています。これに、ブレンド オペレーションを定義する宣言を渡してください。Microsoft Direct3D はシェーダ ハンドルを戻します。CreatePixelShader の呼び出しで、シェーダの有効性確認が行われます。
ピクセル シェーダ パイプラインの実行時のステートは、レンダリング ステートとテクスチャ ステージ ステートを設定しておいて、IDirect3DDevice8::SetTexture、IDirect3DDevice8::SetTextureStageState、IDirect3DDevice8::SetPixelShader、IDirect3Ddevice8::SetPixelShaderConstant の各メソッドを呼び出して制御します。これらのステートと関数は、ピクセル シェーダの制御フローと、ピクセル シェーダ パイプラインのステップの状態を指定します。 Direct3DX ユーティリティ ライブラリには、シェーダをコンパイルするための D3DXAssembleShader と D3DXAssembleShaderFromFileA / D3DXAssembleShaderFromFileW が組み込まれており、これらの関数はピクセル シェーダと頂点シェーダのどちらにも使用できます。
以上でDirectX 8.0 グラフィックスにおけるピクセル シェーダと、プログラマブル パイプラインの説明はおしまいです。
終わりに
シェーダとプログラマブル パイプラインにより、3D プログラマは、創造性を自在に発揮することができ、絶えず進化している 3D グラフィクス ハードウェア機能の最先端を走りつづけることができます。来月は、来たる数か月間にわたる興味深いトピックスの序曲として、新しいサンプル フレームワークを紹介しましょう。
Philip Taylor は DirectX のプログラム マネージャの一人です。DirectX の最初のパブリック ベータの段階から手を染め、かつては実際に DirectX 2 でゲームを開発したこともありました。時間に余裕があるときは、さまざまな3-Dグラフィックス プログラミング メーリング リストで彼に出会うことがあるかもしれません。