連載
.NET&Windows Vistaへ広がるDirectXの世界

第7回 プログラマブル・シェーダによる積極的なGPUの活用

NyaRuRu
Microsoft MVP Windows - DirectX(Jan 2004 - Dec 2007)
2007/05/08

頂点ストリーム→頂点インプット

 頂点シェーダの入力に当たる頂点インプットのデータ構造は、ユーザー定義構造体のようにカスタマイズすることができる。具体例として、サンプル1で使用している頂点シェーダの頂点インプット構造体VertexInを見てみよう。この構造体定義は、C#のソース・コードではなくエフェクト・ファイルにHLSLを用いて記述されている。サンプル1のSimple.fxをご覧いただきたい。

struct VertexIn
{
  float2 Pos : POSITION;
  float2 UV : TEXCOORD0;
};
頂点シェーダの頂点インプット構造体VertexIn(HLSLによる記述)
HLSLでの構造体定義はC言語とよく似ているが、フィールド名の後のコロン記号に続けてPOSITIONやTEXCOORD0という記述があるのが目新しい。
これはセマンティクスと呼ばれるメタデータの一種で、今回はフィールドのUsage(用途)を記述するために使用する。

 先ほど頂点ストリームにセットした頂点バッファは、型情報の失われたBLOB(Binary Large OBject)である。このBLOBのデータ構造と、頂点インプット構造体の各フィールドの対応を記述するスキーマが、XNA FrameworkのVertexDeclarationクラスだ。

 VertexDeclarationクラスのコンストラクタには、頂点の各要素の対応関係を表すVertexElementオブジェクトの配列を指定する。対応関係1つに付き、その情報を含んだVertexElementが1つ存在する。

// 作成
vertexDecl = new VertexDeclaration(
  graphics.GraphicsDevice, new VertexElement[] {
    new VertexElement(
      0, 0, VertexElementFormat.Vector2,
      VertexElementMethod.Default,
      VertexElementUsage.Position, 0),
    new VertexElement(
      0, 8, VertexElementFormat.Vector2,
      VertexElementMethod.Default,
      VertexElementUsage.TextureCoordinate, 0),
});

 ここで、VertexElementコンストラクタ・パラメータは、次のような意味を持っている。

VertexElement(
  ストリーム番号, 要素先頭からのバイト・オフセット, データ型,
  テッセレーション属性,
  用途, 同一用途の区別用ID)

 例えば、上の例で示したVertexDeclarationクラスは2つの要素のVertexElement配列をパラメータに取るが、その意味を図に表してみた。

VertexElement配列により表される対応関係
サンプル・コードで使用しているVertexElement配列の意味を説明すると、次のようになる。
・ストリーム0番の頂点要素には、先頭から0bytes目にVector2型のデータが存在する。データの用途(Usage)は0番目のPosition。
・ストリーム0番の頂点要素には、先頭から8bytes目にVector2型のデータが存在する。データの用途(Usage)は0番目のテクスチャ座標。
ストリーム番号に0以外を選ぶことで、複数の頂点ストリームのデータを集約することもできる。SQLでいえば、複数のテーブルを参照したときのSelect文に近いだろう。
最後のパラメータ(同一Usage区別用のID)は、マルチ・テクスチャでの複数のUV座標など、同じ用途で複数のデータを持つときに使用する。例えば頂点ごとにテクスチャUV座標を2セット持つとすれば、ここに0と1を指定することで、それぞれTEXCOORD0とTEXCOORD1に対応付けることができる。
なお、頂点バッファ上の要素のデータ型と、マッピングされたHLSL要素のデータ型は、必ずしも厳密に一致する必要はない。例えばVector2型からfloat3型に拡張したり、逆にVector4型からfloat2型へ縮小したりといった、いくつかの標準的なデータ変換がデフォルトでサポートされている。

 頂点バッファ→頂点ストリーム→頂点インプットと、データがどのように転送されるかが分かったところで、いよいよ頂点シェーダの実装に移ろう。

頂点インプット→頂点シェーダ→頂点アウトプット

 頂点シェーダとは何かをひと言でいえば、ある頂点インプット構造体VertexOutから頂点アウトプット構造体VertexInへの変換を記述した関数ということになる。.NETの世界では、Converter<VertexOut, VertexIn>デリゲートに適合するメソッドといえばイメージが沸きやすいだろうか。

 頂点インプット構造体VertexInがカスタマイズできたように、頂点アウトプット構造体VertexOutもユーザー定義構造体として定義できる。今回は、次のような頂点アウトプット構造体VertexOutを定義した。

struct VertexOut
{
  float4 ScreenPos : POSITION;
  float2 UV : TEXCOORD0;
};
頂点シェーダの頂点インプット構造体VertexOut(HLSLによる記述)

 頂点シェーダの最も重要な役割は、その頂点がスクリーン上のどこに出力されるかを決めることだ。頂点アウトプット構造体は、従って、スクリーン上の出力位置を表すフィールドを必ず含んでいなければならない。出力位置を表すフィールドは、POSITION Usageによってマークしておく。

 UVフィールドにはTEXCOORD0 Usageが指定されているが、POSITION以外のUsageが設定された出力値は、ラスタライズ後のピクセル・インプットとの連結に使用する。これについては次の章で述べることにしよう。

 さて、POSITION Usageによってスクリーン上の出力位置を指定すると述べたが、このときの座標系はやや特殊で、同次座標(X, Y, Z, W)というものを用いる。同次座標系の点(X, Y, Z, W)は、スクリーン座標系の点(x,y,z)と、

(x, y, z)=(X/W, Y/W, Z/W)

という関係で結ばれる。ただし今回のサンプルでは3Dを扱わないので、詳細については割愛させていただくことにしよう。今回の範囲内では、W=1で固定することで、スクリーン座標系の値を直接出力していると理解しておけばよい。

ビューポートとスクリーン空間の関係
W=1に固定したときは、左図のようなスクリーン座標系の値がそのままX,Y,Zに指定されると思えばよい。
ビューポートとはクライアント領域中の描画対象領域で、デフォルトではクライアント領域全体である。
左図の座標の範囲外に描画した場合は何も表示されない。また、ビューポートのサイズ・形状によらず、スクリーン空間の有効範囲は一定である。
深度バッファ(Zバッファ)が有効なときは、奥行きを考慮した塗りつぶしが行われる。

 実際、今回作成した頂点シェーダは、インプットから渡されたXY座標に、Z=0とW=1を付け加えるだけという単純なものだ。

VertexOut MyVertexShader(VertexIn input)
{
  VertexOut output;
  output.ScreenPos = float4( input.Pos, 0.0f, 1.0f );
  output.UV = input.UV;
  return output;
}
シンプルな頂点シェーダ

 なお、同次座標系が理解できないと3Dゲームが作れないかというと、そんなことはないので安心していただきたい。細かい数学的な計算を自分で行わなくても、必要な変換行列の作成はすべてライブラリに任せることができる。そのあともmul関数を用いてベクトルを行列で変換するだけなので、ほとんどの場合、出力座標の計算は1行で完了する。

 一般に、頂点シェーダの実行順序は表示結果に影響を及ぼせないようになっている。そのおかげで、頂点シェーダを並列に実行し、複数の頂点を同時に処理することが可能なのだ。


 INDEX
  .NET&Windows Vistaへ広がるDirectXの世界
  第7回 プログラマブル・シェーダによる積極的なGPUの活用
    1.トピックの由来とサンプル・コード
    2.描画の流れ(1)
  3.描画の流れ(2)
    4.描画の流れ(3)
    5.描画の実行
 
インデックス・ページヘ  「.NET&Windows Vistaへ広がるDirectXの世界」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間