Unity Shader #1 レンダーパイプライン 1
このノートは主に、Unity のグラフィックスレンダリングパイプラインと頂点・フラグメントシェーダーの基本情報についてです。
基本レンダリングパイプラインの抽象化
基本的なレンダリングプロセスは、下図のように3つの段階に分けられます。
- アプリケーション段階:CPU で発生。
- ジオメトリ段階:GPU で発生。
- ラスタライゼーション段階:GPU で発生。
CPU 段階
Unity では、CPU フェーズで起こることは主に Camera コンポーネントで発生します。Camera コンポーネントはレンダリング時に Render() 関数を呼び出します。この関数は GPU に十分な情報をパッケージ化し、GPU がジオメトリ段階でどのようにレンダリングするかを知ることができます。
一部の情報は CPU によって処理されパッケージ化されます。このデータには以下が含まれます:
カリング
カリングは、レンダリングできないものを決定します。カメラに見えないもの(ビューフラスタムの外)、オクルージョン関係、見えないオブジェクトの裏面など。手動で設定したレンダリングレイヤーのオブジェクトもあります。
レンダーキュー
オブジェクトのレンダリング順序を決定します。Unity では、不透明チャンネル(Geometry)はデフォルトで 2000、利用可能な範囲は <2500。透明チャンネル(Transparent)はデフォルトで 3000、利用可能な範囲は >2500。この値が小さいほど早くレンダリングされます。
データのパッケージ化
頂点位置、頂点法線方向、頂点色、テクスチャ座標などの情報。(.obj) 形式でエクスポートされる情報を観察でき、頂点情報、頂点法線、uv、三角形情報が含まれます。
SetPassCall() と DrawCall()
SetPassCall() はエンジンにどの Shader でレンダリングするかを伝えます。
DrawCall() はエンジンに CPU 側の準備が整ったことを伝え、GPU が描画プロセスを担当することを伝えます。
GPU 段階
GPU 段階では、開発者が行える設定は限られています。より基本的な状況では、基本的に以下のステップに分けられます。
頂点シェーダー
以下を知っておく必要があります:
- 頂点シェーダーは頂点ごとに操作する:その入力は前段階で CPU がパッケージ化した頂点から来る。頂点シェーダーは各頂点に対して1回呼び出される。
- 頂点シェーダー自体は頂点を追加したり減らしたりせず、頂点間の関係(エッジ上にあるか、面を形成するかなど)も知らない。
では、なぜ頂点シェーダーが必要なのでしょうか?パイプライン上、頂点シェーダーの主なタスクは座標変換とライティング計算です。その後、フラグメントシェーダーに必要なデータの一部も頂点シェーダーによって提供されます。通常、Shader ファイルでは v2f 構造体を使用してこの情報を格納します。
頂点シェーダーの最初のタスクは、頂点座標をモデル空間からクリップ空間に変換することです。これをよく理解する必要があります。CPU が渡す(.obj など)ファイルでは、すべての座標情報は元のモデル設定の中心を基準に計算されています。具体的な変換プロセスについては、数学の基礎の章のノートを参照してください。
クリップ
CPU 段階では、最初の粗いカリングプロセスを行いました。そのとき、一部のプリミティブを GPU に渡すことを手動で防ぎ、レンダリングパフォーマンスを向上させました。
GPU 段階では、排除されていない残りのプリミティブに対してさらなるクリップ処理を行う必要があります。いわゆるクリップとは、Camera の可視範囲内にないオブジェクトの処理を指します。
グラフィックス要素とカメラの関係は3つしかありません:完全に見えない、部分的に見える、完全に見える。完全な2つは簡単に処理できます。見えない場合は渡さない。見える場合はパイプラインの次の段階に渡します。クリップ処理は、グラフィックス要素の部分的に見える部分です。
モデル空間からクリップ空間へのプロセスは、写真を撮るプロセスに似ています。この部分は数学の基礎セクションで詳しく説明されますが、今のところ大まかに理解すると:空間内のカメラの位置を記述するには、基本的に以下の情報が必要です:
- 位置:空間内のカメラ自体の位置。Position ベクトルで表す。
- カメラの上方向(Up):カメラの向きを記述するために使用。単位ベクトル Up で表す。
- カメラの方向(Look):カメラの向きを記述するために使用。単位ベクトル Look で表す。
- カメラの右方向(Right):カメラの回転を記述するために使用。単位ベクトル Right で表す。
これら4つのベクトルで、各空間の変換を正確に記述できます。
- モデル空間からワールド空間へ:Model Matrix を左から乗算。このステップの後、モデルを原点とする座標系をワールド原点を原点とする座標系に変換できる。
- ワールド空間からカメラ空間へ:View Matrix を左から乗算。
- カメラ空間からクリップ空間へ:Projection Matrix を左から乗算。ここでの FOV は Field of View(視野角)。aspect_ratio はカメラの幅と高さの比。Zn と Zf はそれぞれニアおよびファークリッピングプレーンの z 座標。
Unity では、組み込みの MVP 行列を使用して、モデル空間からクリップ空間への座標変換を1ステップで完了できます。
クリップは**正規化デバイス座標(NDC)**で行われます。
スクリーンマッピング
NDC 単位立方体内でレンダリングされるクリップされたオブジェクトの3D 座標は、もう明確になっているはずです。次のタスクは、プリミティブの座標をスクリーン座標系に変換することです。
基本的に、このステップは [-1, 1] 上の元の座標をスクリーン解像度の座標範囲にマッピングすると考えられます。ただし、スクリーンは2D なので、このステップでは z 座標を処理しません。
OpenGL と DirectX では y 軸の正方向が異なります。これにより、将来 Shader を書くときに出力画像が y 軸方向で反転する可能性があります。この問題に遭遇したら、単純に *= -1 で対処できます。
ラスタライゼーション段階
ジオメトリ段階で頂点情報を処理しました。このステップでは、プリミティブがカバーするピクセルとこれらのピクセルの色を計算することが目標です。各ピクセルが表示すべき色を計算するプロセスをラスタライゼーションと呼びます。
プリミティブアセンブリ
このステップの目的は、三角形がどのピクセルをカバーするかをチェックすることです。ピクセルグリッドに三角形を描画するプロセスはここでは詳しく説明しません。要約すると、三角形の境界がどのピクセルを通るかを見て、3つの頂点の情報を使用して内部ピクセルを線形補間します。もちろん、すべてのピクセルが頂点からの情報を持っているわけではありません。頂点は点であり、最終的には1つのピクセル内に収まります。したがって、**補間(Interpolation)**を通じて三角形面上の他のすべての点の情報を取得する必要があります。補間とは、両端の値がわかっているときに適切なアルゴリズムで中間値を挿入することです。
ピクセルが三角形にカバーされている場合、このピクセル上にフラグメントが形成されます。フラグメントはピクセルではありません。フラグメントには、スクリーン座標、深度情報、法線座標など、多くの追加情報が含まれるためです。
プリミティブアセンブリの結果、GPU はどのピクセルをシェーディングする必要があるかを知ります。このステップでは、フラグメントシーケンス全体を出力します。このシーケンスの各メンバーの色は、次のフラグメントシェーダーによって処理されます。
フラグメントシェーダー
これまで行ってきたすべてのステップでは、実際の Shade 操作、つまり色付けは行っていません。頂点シェーダーはシェーダーと呼ばれていますが、実際には頂点のすべての情報を提供し、座標変換操作を行うだけです。フラグメントシェーダーに至って、各ピクセルに何色を表示するかを考え始める必要があります。
前のステップのプリミティブアセンブリで補間結果も完了しています。この部分はフラグメントシェーダーの入力値でもあります。つまり、各フラグメントは対応する座標、法線などの情報を知っています。この情報に基づいて表示色の計算を開始する必要があります。これが出力値です。
フラグメントごとの操作
フラグメントシェーダーの制限は、1つのフラグメントとこのピクセル上のフラグメントに起こりうることしか気にしないことです。
フラグメントごとの操作の目的は、フラグメントが最終的に可視かどうかを決定することです。フラグメントは、オクルージョンやマスクなど、一連の理由で最終的に不可視になる可能性があります。したがって、この部分は一連のテストを通過する必要があります。いずれにも合格しない場合は、単純に破棄されます。
- ステンシルテスト(Stencil Test):この段階のテストは、フラグメント上の特定の値と開発者が設定した特定の参照値の関係を比較するためにのみ使用されます。開発者が要求する目的を達成できない場合(例えば、この参照値より大きい場合)、破棄されます。
- 深度テスト(Depth Test):フラグメントの深度値とバッファに既に存在する深度値を比較します。深度テストに失敗したフラグメントは、現在の深度バッファの値を変更する権利がありません。深度テストに合格したフラグメントも、深度バッファの値を更新しないことを選択できます。このステップは開発者が決定します。多くの透明効果では、深度テストでの書き込みのオン/オフが必要です。
- カラーブレンディング(Blend):すべてのテストに合格したら、最後にカラーブレンディングプロセスが行われます。非透明オブジェクトの場合、色バッファ内の他の色を1つの色で直接置き換えることができます(もちろん他の処理方法もあります。主に望む効果によります)。透明オブジェクトの場合は、Multiply や Overlay などの Photoshop のクラシックなレイヤーブレンディング効果など、より複雑なブレンディングアルゴリズムを考慮する必要があるかもしれません。
参考文献:
- World, View and Projection Matrix Internals, http://gsteph.blogspot.com/2012/05/world-view-and-projection-matrix.html
- Unity Shader 入門の基礎、人民郵電出版社、2016年6月初版、馮楽楽著
