Log: KI

雑記ブログです。大体プログラミングのあれこれを書きます。

DirectX11のレンダリングパイプライン

はじめに

この記事はNCC Advent Calendar 2018の参加記事です。

今日は予定を変更して、2日間苦闘を強いられたDirectX11で用意されているレンダリングパイプラインの設定手順を備忘録的にざっくりとまとめます。 特に、昨日一昨日で組んだプログラム(https://github.com/Kuyuri-Iroha/Draw-PMX)に使用した部分の解説になります。説明しないところがたくさんありますので、ご注意ください。

レンダリングパイプラインの処理順序

MicrosoftWindows Dev Centerの「Graphics Pipeline - Windows applications」に全体の動きは載っているのですが、今回使ったところをまとめると以下のような順序で処理が行われます。

  1. インプットアセンブラ
  2. 頂点シェーダー
  3. ラスタライザー
  4. ピクセルシェーダー
  5. アウトプットマージャー

リストアップしてみるとかなり簡素ですね。 この内、プログラマブルなのは頂点シェーダーとピクセルシェーダーの2ステージだけですが、DirectX11では全てのステージをプログラマが初期化する必要があるため、かなり処理が長いです。 それでは、順番に私の理解を説明してゆきます。

1. インプットアセンブラ

Input-Assember、略してIAです。Direct3D 11(DirectX11 SDKの3D処理を担うライブラリ)でもpContext->IASetInputLayout()のように略称が関数名に使われています。 このステージでは主に頂点シェーダーに頂点バッファを渡す処理をします。またこの時、頂点インデックスをもとに頂点バッファーを整理する処理も担います。 また、プリミティブタイプの設定もこのステージに対して行います。これは、Point List や Line List、Triangle List のように、渡した頂点をどう解釈するかの設定となります。

2. 頂点シェーダー

Vertex Shader、略してVSです。このステージでは主に、頂点座標をスクリーン座標系に変換します。 これは3Dの座標系は3軸であるのに対して、描画するスクリーンの座標系は2Dなので2軸です。この異なる座標系間を破綻なく処理するための変換です。

Direct3D 11のスクリーン座標系はだいたいこんな感じになっています。

f:id:kuyuri-iroha:20181217205225p:plain
スクリーン座標系

これはWindowの縦横比が変わっても同じなので、つまり正規化されているということです。

さて、頂点座標変換は

  1. ワールド座標変換
  2. ビュー座標変換
  3. プロジェクション座標変換

の順で行われます。 これは、3Dのキャラクターを描画する時に例えるならば、

  1. 3Dキャラクターの現在位置、回転、スケーリングを頂点に適用する。
  2. カメラから見た相対座標系に変換する。
  3. 2Dのスクリーン座標系に落とし込む。

というプロセスであるとも考えられます。 上記の操作をすることで、3Dの空間データが2Dのスクリーンに映し出されるわけですね。

ちなみに、後のラスタライザで深度カリングをしたり、アウトプットマージャーで深度テストをするためにZ座標を捨てるわけではありません。 また、法線やUV座標は法線マップなどのテクスチャ形式でない限りは頂点と対応した形で用意されることがほとんどなので、頂点バッファに一緒にパッキングします。加えて、今回行った処理内容では頂点シェーダーでの加工はせず、そのまま次のステージに渡してしまいます。

3. ラスタライザー

Rasterizer、略してRSです。このステージでは主に、ポリゴンの表裏判定、ポリゴンが覆っているピクセルの選定、頂点シェーダーから渡された値の線形補間を行います。

また、深度テストで重なるポリゴンのピクセルを破棄する(ピクセルシェーダーに渡さない)ようにしたり、スクリーン座標系からはみ出てしまった部分を破棄する。というような、レンダリングパイプラインの中でも相当重要な処理を担います。

Direct3D 11やOpenGLでは1回の描画内で他の面に重なって見えなくなってしまうピクセルを破棄する深度カリングの無効化や、ポリゴン裏のピクセル破棄の無効化などの設定はできますが、基本的にはブラックボックスとなっています。

このラスタライザーを通して、頂点から生成されたジオメトリをピクセルシェーダーで1ピクセルずつ処理できるように加工して渡します。

4. ピクセルシェーダー

Pixel-Shader、略してPSです。このステージでは言わずもがなピクセルの色を決定します。

例えば、前日の「HLSLでトゥーンレンダリングをしたかったお話」で遊んでいたときの

f:id:kuyuri-iroha:20181217011429p:plain
法線描画
これは、頂点バッファとして1頂点と対応する形でGPUに送った法線をラスタライザーで線形補間した(x, y, z)というベクトルをそのまま(r, g, b)という風に色情報としてアウトプットマージャーに渡して描画したものです。 Direct3Dでは左手座標系を使っているので、画面に向かって右方向(X軸方向)に向いている面が赤く、上方向(Y軸方向)に向いている面が緑色になっている事がわかります。(Z軸方向はこの時画面に向かって奥側となっているので、青い面はこの視点からは確認できません。)

また、あまり使い所はありませんが、ただただ単色に塗りつぶすこともできますし、Direct3Dのシェーダー言語であるHLSLの文法的に間違っていなければ、結構自由に塗り絵が楽しめます。ちなみに、送出するデータ形式(r, g, b, a)成分の4次元ベクトルとなります。

さらに、ここで複数の描画ターゲットに対して別の色(データ)を送出することができます。これが、前回の記事で苦しんだMRT (Multi Rendering Target) です。

5. アウトプットマージャー

Output-Merger、略してOMです。このステージでは、ピクセルシェーダーから送出されたピクセル値(色)を描画ターゲットと呼ばれるテクスチャの一種に書き出します。 この時、ブレンドステートや、デプスステンシルステートという設定オブジェクトによって、描画ターゲットにもともと描画されているピクセル値とどうブレンドするか、または完全に上書きするかなどの色に関する処理方法や、深度テストと呼ばれる描画ターゲットにもともと書き込まれているピクセルより奥になるピクセルを描画しないようにする処理の有効化・無効化などを設定できます。

また、ここではステンシルに関しては私の中でイメージができていないため説明を省略させていただきます。ただ、今回のプログラムではステンシルテストを無効化しているため、あまり効果はないものとなっています。

描画完了

ざっくりと以上のステージを経て描画が完了します。 バックバッファに描画してスクリーンに表示するには、予めバックバッファ用の描画ターゲットビューオブジェクトと呼ばれるものを用意して、そこへ描画する必要がありますが、今回はレンダリングパイプラインというトピックからは若干逸れるので説明を省きます。

また、プログラマブルシェーダーへ定数としてのデータを渡すことに焦点を当てると、定数バッファであったり、シェーダリソースビュー、サンプラーステートなどがあります。特に定数バッファに関してはDirect3D 11では4 byteで割り切れるデータサイズである必要があるため注意することも多いです。

おわりに

今回は予定を変更してレンダリングパイプラインの私の理解を大雑把に説明しました。 勢いで書いている節があるのと、あくまで「私の理解」であると言うことで、詳しく知りたい方は序盤にも紹介した

Graphics Pipeline - Windows applications

を読めば正確で詳細な情報が手に入るので、そちらを参照ください。