いまさらDirect3D11入門

初めてグラフィックスAPIを触る人に向けて

ジオメトリシェーダ

今回はジオメトリシェーダについて見ていきます。

ジオメトリシェーダはDirect3D10から追加されたステージでシェーダモデル4.0以上に対応したGPUで使用できます。 グラフィックスパイプラインではラスタライザステージの前に実行されます。 特徴はGPU内で線分や三角形を生成することが可能な点でしょう。 さらに頂点シェーダでは1つの頂点の情報しか見れませんでしたが、ジオメトリシェーダではペアとなる他の頂点の情報も見ることが出来ます。 ジオメトリシェーダを利用することにより、グラフィックスパイプラインはより自由度の高い処理が行えるようになります。

ドキュメント:ジオメトリシェーダ (日本語) (英語)

概要

今パートではジオメトリシェーダの書き方について見ていきます。 対応するプロジェクトはPart10_GeometryShaderになります。

ジオメトリシェーダ

シェーダ

ジオメトリシェーダは以下のように実装します。

//GeometryShader.hlsl
struct GSOutput
{
  float4 pos : SV_POSITION;
  float4 color : COLOR0;
};
static const float4 trianglePos[3] = {
  float4(0.f, 0.5f, 0.f, 0.f),
  float4(0.5f, -0.5f, 0.f, 0.f),
  float4(-0.5f, -0.5f, 0.f, 0.f),
};
//maxvertexcount属性:最大3つの頂点を生成することを宣言している
[maxvertexcount(3)]
void main(
  point float4 input[1] : POSITION,
  inout TriangleStream< GSOutput > output//三角形を生成するときに使うもの
){
  //点から三角形を生成するシェーダ
  //三角形を構成する頂点の生成するループ
  [unroll] for (uint i = 0; i < 3; i++)
  {
    GSOutput element;
    element.pos = input[0] + trianglePos[i];
    element.color = float4(1.0f, 1.0f, 0.3f, 1.f);
    output.Append(element);//ここで頂点を生成している
  }
  output.RestartStrip();//Append関数で生成した頂点を三角形として構成している
}

頂点シェーダやピクセルシェーダと違って、キーワードなどが多いですが1つずつ見ていきましょう。

maxvertexcount属性

maxvertexcount属性はジオメトリシェーダが生成する頂点の最大数を指定する属性になります。 必ず記述してください。 カッコの中の数字が最大数になります。

プリミティブ型

まず、main関数の引数にあるpointキーワードです。

//頂点シェーダから点情報を受け取っている
//必ず配列にすること
void main(
  point float4 input[1] : POSITION,
  ...
//線分の場合
  line float4 input[2] : POSITION,
//三角形の場合
  triangle float4 input[3] : POSITION,

pointキーワードはジオメトリシェーダの入力として点情報が渡されることを表しています。 もちろん、線分や三角形のものもそれぞれlinetriangleとして用意されています。 また、線分と三角形には隣接する頂点も受け取るlineadjtriangleadjも用意されています。 この2つを使用するときは頂点バッファ(またはインディクスバッファ)の並びを専用の並びにする必要がありますので注意してください。 日本語の方はキーワードの説明までしか書かれていませんので、その詳細は英語ドキュメントを参考にしてください。
ドキュメント: ジオメトリ シェーダー オブジェクト Geometry-Shader Object

プリミティブ型一覧

  • point:点情報
  • line:線分情報
  • triangle:三角形情報
  • lineadj:隣接する頂点も含む線分情報
  • triangleadj:隣接する頂点も含む三角形情報

ストリーム出力オブジェクト

次は新しいプリミティブを生成するとき使うオブジェクトです。

//TriangleStreamは三角形を生成するときに使うもの
void main(
  ...
  inout TriangleStream< GSOutput > output){
//線分の場合
  inout LineStream< GSOutput > output
//点の場合
  inout PointStream< GSOutput > output

TriangleStreamが三角形の生成を行います。 テンプレート引数のように<…>の中で頂点の型を指定します。 必ず、inoutキーワードを付けてください。

ストリーム出力オブジェクトには2つのメンバ関数が用意されています。

  • Append:頂点データをストリーム追加する
  • RestartStrip:追加した頂点でプリミティブを構成し、新しいプリミティブの生成を開始する

関数の詳しい説明はドキュメントを参考にしてください。
ドキュメント: ストリーム出力オブジェクト Stream-Output Object

ジオメトリシェーダでは以上の要素を使って実装します。 CPU側ではジオメトリシェーダはID3D11GeometryShaderと表現されています。 シェーダのコンパイル時にはシェーダモデルに“gs_”を指定してコンパイルしてください。 ID3D11GeometryShaderの作成とグラフィックスパイプラインへの設定は他のシェーダと似ていますので省略します。 サンプルを御覧ください。

頂点バッファを使わない三角形を描画

ジオメトリシェーダを使用すると頂点バッファを使わずともプリミティブを描画することが可能です。 その時は空の構造体をジオメトリシェーダの入力として受け取る必要があります。

// GeometryShader2.hlsl
//ダミーの入力値
//前のステージから何も受け取らない時はダミー構造体を受け取る必要がある
struct DummyInput {};
struct GSOutput
{
  float4 pos : SV_POSITION;
  float4 color : COLOR0;
};
static const float4 trianglePos[3] = {
  float4(0.f, 0.5f, 0.f, 1.f),
  float4(0.5f, -0.5f, 0.f, 1.f),
  float4(-0.5f, -0.5f, 0.f, 1.f),
};
[maxvertexcount(3)]
void main(
  point DummyInput input[1] : POSITION,
  in uint id : SV_PrimitiveID,
  inout TriangleStream< GSOutput > output
  )
{
  [unroll] for (uint i = 0; i < 3; i++) {
    GSOutput element;
    element.pos = trianglePos[i];
    element.color = float4(0.3f, 1.0f, 1.0f, 1.f);
    output.Append(element);
  }
}

上のSV_PrimitiveIDというシステムセマンティクスは描画中のプリミティブの識別子になります。 例えば、三角形を10個描画したとすると、SV_PrimitiveIDは0~9の値を各三角形に振り分けます。 頂点バッファを使用しない場合はシェーダリソースからデータを取得することになると思いますので、その時に使用するシステムセマンティクスになります。

何もしない頂点シェーダ

頂点バッファを使用しない時は何もしない頂点シェーダを用意する必要があります。 このシェーダを使うときは入力レイアウトは必要ありません。

// VSDummy.hlsl
//何もしない頂点シェーダ
void main()
{}

まとめ

以上でジオメトリシェーダの書き方についての説明は終わります。 ジオメトリシェーダはプリミティブを生成できる特徴からかなり自由度の高いシェーダになります。 デバッグ用の簡易な箱をシェーダのみで生成できたりとちょっとしたこともできますので活用していきましょう。

また、グラフィックスパイプラインで処理したプリミティブをバッファに出力することができます。 これについては次のパートで見ていきます。

<前 トップ 次>