作業を開始する
Unreal Engine 4 (UE4) にはレンダリング コードが数多くあるため、概要を把握するのは大変です。コードを使った読み取りは、新規フレームがレンダリング スレッド上でレンダリングされる FDeferredShadingSceneRenderer::Render から開始するのが良いでしょう。profilegpu コマンドを実行し、描画イベントを調べるのも便利です。Visual Studio で描画イベントに対して [Find in Files (ファイルを検索)] を実行して、対応する C++ インプリメンテーションを検索します。
レンダリング処理に便利はコンソール コマンドです (? をパラメータとして使用し現在のステートにパラメータがない場合は通常ヘルプがでます)。
コンソール コマンド | 説明 |
---|---|
stat unit | 全体的なフレーム時間、およびゲームスレッド、レンダリング スレッド、GPU の時間を表示します。時間が最長の方がボトルネックになっています。しかし、 GPU 時間にはアイドル時間が含まれます。そのため、最長でスタンドアロンであれば、 GPU 時間だけがボトルネックになります。 |
Ctrl+Shift+. または recompileshaders changed | 最後に .usf ファイルを保存してから変更されたシェーダーを再コンパイルします。ロード中に自動的に行われます。 |
Ctrl+Shift+; または profilegpu | レンダリングされているビューに対して GPU のタイミングを測定します。その結果をポップアップする UI またはエンジン ログで見ることができます。 |
Vis または VisualizeTexture | bmp として保存可能な機能で各種レンダリング ターゲットのコンテンツを視覚化します。 |
show x' | 特定の表示フラグを切り替えます。Show を使って表示フラグと現在のステートのリストを取得します。エディタでは、代わりにビューポート UI を使います。 |
pause | ゲームを一時停止しますが、レンダリングは続行します。シミュレーション レンダリング作業を全て停止します。 |
slomo x | ゲーム速度を変更します。プロファイル時にシミュレーション作業をスキップせずに、時間を遅くするのに非常に便利です。例えば slomo .01 |
debugcreateplayer 1 | テスト用の分割スクリーンです。 |
r.CompositionGraphDebug | 実行すると 1 つのフレームの構成グラフの単一フレーム ダンプを取得します (ポストプロセスとライティング)。 |
r.DumpShaderDebugInfo | 1 に設定すると、後にコンパイルされるシェーダーが GameName/Saved/ShaderDebugInfo にデバッグ情報をダンプします。 |
r.RenderTargetPoolTest | 特別な色でレンダリング ターゲット プールで返されたテクスチャを初期化して、色漏れバグを追跡します。 |
r.SetRes | 現在のゲームビューの表示解像度を設定します。エディタではエフェクトはありません。 |
r.ViewportTest | エディタを使用する際に発生する様々なビューポートの矩形設定 (ゲームでのみ) のテストが可能になります。 |
レンダリング作業時に便利なコマンドラインです。
コマンドライン | 説明 |
---|---|
-d3ddebug | D3D11 デバッグ レイヤーを有効にします。 API エラーの探知に便利です。 |
-sm4 | D3D11 RHI で機能レベル SM4 を強制します。 |
-opengl3 / -opengl4 | 指定した機能レベルで強制的に Open GL RHI を使用します。 |
-dx11 | Windows の現在のデフォルトです。 |
-dx12 | 実験的です。 |
-featureleveles2 | エディタで起動すると無視されるので、UI を使う必要があります。 |
-featureleveles31 | 実験的です。エディタで起動すると無視されるので、Editor Preferences/Experimental/Rendering で有効にしておく必要があります。 |
-ddc=noshared | ネットワーク上の (共有の) 派生データのキャッシュを使用しないようにします。シェーダー キャッシュの問題をデバッグする場合に便利です。 |
モジュール
モジュール内に存在するレンダリング コードは、非モノリシック ビルド用に dll にコンパイルされます。そのため、アプリケーション全体を再度リンクする必要がなくなり、コード変更のレンダリング時のイタレーションが速くなります。レンダラのモジュールはエンジンへの呼び戻しが多いのでエンジンに依存します。ただし、エンジンがレンダラの中でコードの呼び出しが必要な場合、通常は IRendererModule か FSceneInterface のインターフェース経由で行われます。
シーンの表現
Unreal Engine 4 では、レンダラが見るシーンは、Fscene に格納されている様々な種類の他の構造体のプリミティブ コンポーネントとリストによって定義されます。プリミティブの octree は空間クエリを加速するために更新されます。
主要なシーン クラス
UE4 にはゲーム スレッドと平行して動く レンダリング スレッド があります。ゲーム スレッドとレンダリング スレッド間のギャップを埋めるクラスのほとんどは、そのステートのオーナーシップをどちらのスレッドが保有するかによって 2 つに分かれます。
主要なクラスは以下のとおりです。
クラス | 説明 |
---|---|
UWorld | ワールドには、お互いが相互作用するアクタとコンポーネントのコレクションが含まれます。レベルはワールドの内外へストリーミングすることができ、プログラム内で複数のワールドをアクティブすることができます。 |
ULevel | 一緒にロード / アンロードされて 1 つのマップファイルに保存されたアクタとコンポーネントのコレクションです。 |
USceneComponent | ライト、メッシュ、フォグなど、 FScene に追加する必要がのあるオブジェクトの基本クラスです。 |
UPrimitiveComponent | レンダリングあるいは物理との相互作用が可能な基本クラスです。また、可視性カリングの詳細度とレンダリング プロパティ の指定 (シャドウのキャストなど) としての役割もあります。全ての UObjects と同様、ゲーム スレッドは全ての変数とステートを所有し、レンダリング スレッドが直接アクセスしません。 |
ULightComponent | 光源を表現します。レンダラは、効果を計算しシーンに追加する役目があります。 |
FScene | Uworld のレンダラ版です。オブジェクトは FScene に追加されるとレンダラに対してのみ存在し、コンポーネントの登録のために呼び出されます。レンダリング スレッドは全てのステートを FScene 上に所有し、ゲーム スレッドは直接それを修正することができません。 |
FPrimitiveSceneProxy | UPrimitiveComponent のレンダラ バージョンで、スレッドをレンダリングするために UPrimitiveComponent ステートをミラーします。エンジン モジュールに存在し、異なる種類のプリミティブ (スケルタル、剛体、 BSP など) をサポートのためにサブクラス化されます。非常に重要な関数のうち GetViewRelevance や DrawDynamicElements などを実行します。 |
FPrimitiveSceneInfo | UPrimitiveComponent と FPrimitiveSceneProxy に対応した内部のレンダラ ステート (FRendererModule の実行専用) です。レンダラ モジュールに存在するので、エンジンは見ることができません。 |
FSceneView | エンジンを FScene に 1 つのビューで表します。シーンは、異なるビューで FSceneRenderer::Render に対する異なる呼び出し (複数のエディタ ビューポート) でのレンダリング、または複数のビューで FSceneRenderer::Render に対する同じ呼び出し (ゲームにおける分割スクリーン) でのレンダリングが可能です。フレームごとに新規ビューが構成されます。 |
FViewInfo | 内部のビューを表します。レンダラ モジュールに存在しています。 |
FSceneViewState | ViewState はフレーム全体に必要なビューに関する private のレンダラ情報を格納します。ゲームでは、ビューステートは ULocalPlayer につき 1 つです。 |
FSceneRenderer | 中間テンポラリをカプセル化するためにフレームごとに作成されるクラスです。 |
以下は、属するモジュール別に整理した主要クラスのリストです。リンカー問題の解決方法を探す場合に重要になります。
エンジン モジュール | レンダラ モジュール |
---|---|
UWorld | FScene |
UPrimitiveComponent / FPrimitiveSceneProxy | FPrimitiveSceneInfo |
FSceneView | FViewInfo |
ULocalPlayer | FSceneViewState |
ULightComponent / FLightSceneProxy | FLightSceneInfo |
同じクラスをステートのオーナーシップを所有するスレッド別に整理しました。競合状態を避けるために 、コードを書いたステートを所有するスレッドがどれかを常に覚えておくことが重要です。
ゲーム スレッド | レンダリング スレッド |
---|---|
UWorld | FScene |
UPrimitiveComponent | FPrimitiveSceneProxy / FPrimitiveSceneInfo |
FSceneView / FViewInfo | |
ULocalPlayer | FSceneViewState |
ULightComponent | FLightSceneProxy / FLightSceneInfo |
マテリアル クラス
クラス | 説明 |
---|---|
FMaterial | レンダリングに使用されるマテリアルのインターフェースです。マテリアルのプロパティ (ブレンドモードなど) へのアクセスを提供します。シェーダーを個別に抽出するためにレンダラが使用するシェーダー マップを含みます。 |
FMaterialResource | UMaterial が FMaterial インターフェースを実装します。 |
FMaterialRenderProxy | レンダリング スレッドでのマテリアルの表示です。FMaterial インターフェース、スカラー、ベクター、テクスチャ パラメータへのアクセスを提供します。 |
UMaterialInterface | [abstract] マテリアルの機能に対するゲーム スレッド インターフェースです。レンダリングに使用する FMaterialRenderProxy とソースとして使用される UMaterial を取得します。 |
UMaterial | マテリアル アセットです。ノード グラフとして作成されます。シェーディングに使用するマテリアル属性を計算し、ブレンドモードなどを設定します。 |
UMaterialInstance | [abstract] UMaterial のインスタンスです。UMaterial においてノード グラフを使用しますが、異なるパラメータ (スカラー、ベクター、テクスチャ、静的スイッチなど) を提供します。インスタンスはそれぞれ親 UMaterialInterface を持ちます。従って、マテリアル インスタンスの親は UMaterial または別の UMaterialInstance となる場合があります。これにより、最終的には UMaterial へつながるチェーンが作成されます。 |
UMaterialInstanceConstant | エディタ内でのみ変更できる UMaterialInstance です。スカラー、ベクター、テクスチャ、静的スイッチなどを提供できます。 |
UMaterialInstanceConstant | 実行時のみ変更できる UMaterialInstance です。スカラー、ベクター、テクスチャ、静的スイッチなどを提供できます。静的スイッチ パラメータを提供することはできず、別の UMaterialInstance の親になることはできません。 |
プリミティブ コンポーネントとプロキシ
プリミティブ コンポーネントは、可視性と関連性を判断する基本ユニットです。例えば、オクルージョンと視推体カリングはプリミティブごとに行われます。従って、システム作成時に、作成するコンポーネントの大きさを考えることが重要です。それぞれのコンポーネントには、カリング、シャドー キャスティング、ライトの影響の判断などの各種操作に使用するバウンドがあります。
コンポーネントは登録された場合のみ、シーン (つまりレンダラ) で可視化されます。コンポーネントのプロパティを変更するゲーム スレッド コードは、コンポーネント上で MarkRenderStateDirty() を呼び出し、レンダリング スレッドに対して変更を伝搬しなければなりません。
FPrimitiveSceneProxy と FPrimitiveSceneInfo
FPrimitiveSceneProxy は、コンポーネントの種類によってサブクラス化されることになる UPrimitiveComponent のレンダリング スレッド版です。エンジン モジュールに存在し、レンダリング パス中に呼び出される関数を持っています。FPrimitiveSceneInfo は、レンダラ モジュールに対して private なプリミティブ コンポーネント ステートです。
重要な FPrimitiveSceneProxy メソッド
関数 | 説明 |
---|---|
GetViewRelevance | フレームの最初に InitViews から呼び出され、エントリされる FPrimitiveViewRelevance を返します。 |
DrawDynamicElements | プロキシが関連しているすべてのパスにあるプロキシを描画するために呼び出されます。動的に関連性があることをプロキシが示した場合のみ呼び出されます。 |
DrawStaticElements | プリミティブがゲーム スレッド上にアタッチされると、プロキシに対してスタティックメッシュ エレメントをサブミットするために呼び出されます。静的に関連性があることをプロキシが示した場合のみ、呼び出されます。 |
シーン レンダリングの順序
レンダラは、データを合成したい順序でレンダラ ターゲットに対してシーンを処理します。例えば、深度のみのパスはベース パスの前にレンダリングされるので、ベース パスのシェーディング負荷を減らすために Heirarchical Z (HiZ) がエントリされます。この順序は C++ で呼び出される order pass 関数によって静的に定義されます。
関連性
FPrimitiveViewRelevance は、エフェクト (従ってパス) とプリミティブとの関連性に関する情報です。プリミティブは関連性の異なる複数のエレメントをもつ場合があるので、FPrimitiveViewRelevance は効果的にロジカルな関連性あるいは全てのエレメントの関連性となります。つまり、プリミティブは不透明および透過な関連性、あるいは動的と静的な関連性を併せ持つことが可能であり、相互排他的ではないということになります。
さらに FPrimitiveViewRelevance は、プリミティブが bStaticRelevance や bDynamicRelevance で動的または静的レンダリング パスを使う必要があるかどうかを示します。
描画ポリシー
描画ポリシーには、パス専用シェーダーでメッシュをレンダリングするためのロジックが含まれます。メッシュタイプの取得には FVertexFactory インターフェースを、マテリアルの詳細の抽出には FMaterial インターフェースを使用します。詳細レベルで、メッシュ マテリアル シェーダーセットと頂点ファクトリを受け取り、頂点ファクトリのバッファを Rendering Hardware Interface (RHI) に結合し、メッシュ マテリアル シェーダーを RHI に結合し、適切なシェーダー パラメータを設定し、 RHI ドロー コールを発行します。
描画ポリシー メソッド
関数 | 説明 |
---|---|
Constructor | 任意の頂点ファクトリとマテリアル シェーダー マップから適切なシェーダーを探し、これらのリファレンスを格納します。 |
CreateBoundShaderState | 描画ポリシー用の RHI バウンド シェーダー ステートを作成します。 |
Matches/Compare | 静的描画リストにある他のもので描画ポリシーをソートするメソッドです。Matches は DrawShared が依存する全ての係数を比較しなければなりません。 |
DrawShared | Matches から true を返した描画ポリシーの間で一定の RHI ステートを設定します。例えば、ほとんどの描画ポリシーはマテリアルと頂点ファクトリ上でソートするので、マテリアルのみに依存するシェーダー パラメータの設定が可能で、頂点ファクトリ固有の頂点バッファの結合が可能です。DrawShared は静的レンダリング パスでは呼び出し回数が少ないので、できれば SetMeshRenderState の代わりにステートを常にここに設定すべきです。 |
SetMeshRenderState | このメッシュに固有の RHI ステート、または DrawShared に設定されていないものを設定します。DrawShared よりも遥かに呼び出し回数が多いので、パフォーマンスが非常に重要です。 |
DrawMesh | RHI ドローコールを実際に発行します。 |
レンダリング パス
UE4 には、制御能力は大きくトラバース速度が遅い動的パスと、 RHI レベルにできるだけ近くでシーン トラバースをキャッシュする静的レンダリング パスがあります。両者とも詳細レベルでは描画ポリシーを使用するので、違いはほとんど大まかなレベルです。それぞれのレンダリング パス (描画ポリシー) は必要に応じて両方のレンダリング パスに確実に対処する必要があります。
動的レンダリング パス
動的レンダリング パスは TDynamicPrimitiveDrawer を使い、それぞれのプリミティブ シーン プロキシ上に DrawDynamicElements を呼び出してレンダリングします。レンダリングされるために動的パスを使用する必要のあるプリミティブは FViewInfo::VisibleDynamicPrimitives でトラックされます。それぞれのレンダリング パスはこの配列を繰り返し、それぞれのプリミティブのプロキシ上で DrawDynamicElements を呼び出す必要があります。その後、プロキシの DrawDynamicElements は FMeshElements を必要な数だけ集めて DrawRichMesh あるいは TDynamicPrimitiveDrawer::DrawMesh でサブミットする必要があります。その結果、CreateBoundShaderState、DrawShared、SetMeshRenderState、最終的には DrawMesh を呼び出す一時的な描画ポリシーが新規作成されます。
それぞれのプロキシはそのコンポーネント タイプ固有のロジックを実行できる DrawDynamicElements にコールバックがあるので、動的レンダリング パスは非常に柔軟です。さらに、ステートのソート処理がなく、何もキャッシュされないので、挿入負荷は最低限ですがトラバース負荷は高くなります。
静的レンダリング パス
静的レンダリング パスは、静的描画リストによって実装されます。メッシュはシーンにアタッチされると描画リストに挿入されます。挿入されている間にプロキシ上に DrawStaticElements が呼び出されて FStaticMeshElements がコレクトされます。すると、CreateBoundShaderState の結果に合わせて描画ポリシー インスタンスが作成および格納されます。Compare 関数および Matches 関数に合わせて新規の描画ポリシーがソートされ、描画リストの適切な位置に挿入されます (TStaticMeshDrawList::AddMesh 参照)。InitViews では静的描画リスト用の可視性データを含むビット配列が初期化され、描画リストが実際に描画される TStaticMeshDrawList::DrawVisible へ渡されます。DrawShared はお互いに一致するすべての描画ポリシーに対して 1 回だけ呼び出されますが、SetMeshRenderState と DrawMesh は FStaticMeshElement ごとに呼び出されます (TStaticMeshDrawList::DrawElement 参照)。
静的レンダリング パスはかなりの作業をアタッチ時へ移動させるので、レンダリング時のシーン トラバースが大幅に高速化されます。静的描画リストのレンダリングは、スタティックメッシュのレンダリング スレッドで速度が約 3 倍になるので、さらに多くのスタティックメッシュをシーンに持つことが可能になります。静的描画リストはアタッチ時にデータをキャッシュするので、ビューを個々のステートでのみキャッシュできます。再アタッチされることが滅多になく頻繁にレンダリングされるプリミティブは、静的描画リストの候補として最適です。
静的レンダリング パスは、ステート バケットにつき 1 回だけ DrawShared を呼び出す方法なので、バグのエクスポートができます。これらのバグは、シーンでメッシュをレンダリングおよびアタッチする順序に依存するため、検出が難しいです。ライティングのみや、unlit (ライティング無し) などの特別なビューモードは、全てのプリミティブに動的パスを強制的に使用させます。そのため、動的レンダリング パスを強制した結果バグがなくなった場合は、描画ポリシーの DrawShared 関数あるいは Matches 関数の実装が正しくなかったことだと分かります。
レンダリングのおおまかな順序
以下は、 FDeferredShadingSceneRenderer::Render: で始まるフレームのレンダリング時の制御フローの説明です。
操作 | 説明 |
---|---|
GSceneRenderTargets.Allocate | 必要に応じて、現在のビューに対して十分な大きさになるようにグローバル シーン レンダー ターゲットを再配置します。 |
InitViews | 必要に応じて、様々なカリング メソッドでビューに対してプリミティブの可視性を初期化し、このフレームで表示できる動的シャドウを設定し、シャドウ視錘台をワールドと交差させます (シーン シャドウ全体またはプリシャドウ)。 |
PrePass / Depth only pass | RenderPrePass / FDepthDrawingPolicy.深度を深度バッファに出力して、オクルーダをレンダリングします。このパスは、無効、オクルージョンのみ、あるいは深度といったように、アクティブになっている機能に必要なものに合わせて幾つかのモードで動作することができます。このパスは通常、ピクセル シェーダーの負荷が大きいベース パスのシェーディング負荷を軽減するために階層的 Z バッファを初期化する目的で使用されます。 |
Base pass | RenderBasePass / TBasePassDrawingPolicy です。マテリアル属性を GBuffer へ出力して、不透明なマスク付マテリアルをレンダリングします。ライトマップ効果と天空光もここで計算され、シーン カラーに入ります。 |
Issue Occlusion Queries / BeginOcclusionTests | 次のフレームの InitViews で使用されることになる潜在的なオクルージョン クエリを開始します。そのためには、クエリ先のオブジェクトの周りのバウンディング ボックスをレンダリングしますが、時にはバウンディング ボックスをまとめてグループ化して描画コールを減らすこともあります。 |
Lighting | シャドウマップはライトごとにレンダリングされ、標準のディファードとタイル処理されたディファード・シェーディングを混ぜることで、ライト効果はシーンカラーに累積されます。ライトは透過ライティング ボリュームにも蓄積されます。 |
Fog | フォグと環境はディファード パスで不透明なサーフェスに対しピクセルごとに計算されます。 |
Translucency | 透過は、フォグが頂点ごとに適用されている オフスクリーン レンダー ターゲットへ蓄積されるので、シーンへの統合も可能です。Lit の透過は 1 つのパスでの最終的なライティングを計算して正確にブレンドします。 |
Post Processing | GBuffers を使って各種ポストプロセス エフェクトが適用されます。透過はシーンに合成されます。 |
かなり簡素化されたおおまかなビューです。詳細は、 'profilegpu' の関連コードかログ出力を調べてください。
Render Hardware Interface (RHI)
RHI は、プラットフォーム専用のグラフィック API 上の薄いレイヤーです。UE4 の RHI 抽出レベルはできる限り詳細レベルになっています。目的は、ほとんどの機能はプラットフォーム非依存コードで書くことができ、要求される機能レベルをサポートする全てのプラットフォームで「動作するだけ」にすることです。
複雑度を低く保つために、機能セットは ERHIFeatureLevel の中で量子化されます。プラットフォームが機能レベルで要求される機能の全てをサポートできない場合は、サポートできるレベルまで落とします。
機能レベル | 説明 |
---|---|
SM5 | 一般的には D3D11 Shader Model 5 に対応していますが、OpenGL 4.3 制限のため 16 テクスチャのみ使用することができます。テセレーション、演算シェーダー、キューブマップ配列がサポートされています。ディファード・シェーディング パスはサポートされます。 |
SM4 | D3D11 Shader Model 4 に対応します。SM5 と全体的に一緒ですが、テセレーション、演算シェーダー、キューブマップ配列がありません。ディファード・シェーディング パスがサポートされています。明暗順応は演算シェーダーを使用するため、サポートされていません。 |
ES3_1 | OpenGL ES3.1、Vulkan、Metal にサポートされている機能に対応します。 |
レンダリング ステートのグループ化
レンダリング ステートは、影響を与えているパイプラインの部分によってグループ化されます。例えば、RHISetDepthState は深度バッファに関連する全てのステートを設定します。
レンダリング ステートのデフォルト
レンダリング ステートは数が多いので、描画のたびに一つ一つ設定するのは大変です。UE4 には、デフォルト設定を前提にした暗示的なステートが入っているので (従って変更した場合はこれらのデフォルトにリストすること)、明示的が設定が必要なステートはかなり少ないです。以下のステートには暗示的なデフォルトがありません。
- RHISetRenderTargets
- RHISetBoundShaderState
- RHISetDepthState
- RHISetBlendState
- RHISetRasterizerState
- RHISetBoundShaderState で設定されるシェーダーの依存
その他のすべてのステートはデフォルト状態を想定します (例えば、関連する TStaticState で定義されているようにデフォルトのステンシル ステートは RHISetStencilState(TStaticStencilState<>::GetRHI())
で設定されます)。