UE4の描画パスについて Ver 4.18.1

こちらは Unreal Engine 4 Advent Calender 2017 16日目の記事です。

以前書いたUE4描画パスの順序をまとめたものがだいぶ古くなってしまっていたので最新版へのアップデート。
以前の記事はこちらです。

monsho.hatenablog.com

動作を完全に確認しているわけではないので、ミスがあるかもしれません。
見かけたらお伝えいただければ。
なお、今回もDeferred Renderingパスに関してのみで、モバイルやForward Renderingパスについては言及しません。
RenderDocを見ながらチェックする方が読む側は楽しいと思いますが、描画パスを詳しく知りたい、あわよくば改造したいという人はRenderDocよりソースコードの行数やコード片の方がうれしいかな、と思いまして。

Deferred Renderingの開始は

Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cpp (532)
FDeferredShadingSceneRenderer::Render()

ここから開始です。

行数 コード片 内容
539 GRenderTargetPool.TransitionTargetsWritable() ターゲットプールのターゲットを描画可能な状態に遷移
542 SceneContext.ReleaseSceneColor() シーンカラーバッファを解放
546~552 FRHICommandListExecutor::WaitOnRHIThreadFence() RHIが別スレッドで動作している場合、ここで OcculusionSubmittedFence を待つ
前段のOcculusionCulling処理の終了待ち?
568 GSystemTextures.InitializeTextures() システムテクスチャの初期化
初期化済みならなにもしない
571 SceneContext.Allocate() ViewFamilyに見合ったレンダーターゲット用のバッファを確保する
ViewFamilyはFeatureLevelやSceneCaptureがあるかなどの情報
573 SceneContext.AllocDummyGBufferTargets() GBufferにダミーの黒テクスチャを割り当て
579 InitViews() Viewに関する様々な初期化
メッシュのカリング、半透明のソート、シャドウやライトキャッシュの初期化など多岐にわたる
詳しくはソースを追ってほしい
581~588 PostInitViewFamily_RenderThread()など 未実装っぽい
599~606 GetEyeAdaptation() const関数を呼び出してるが取得したものを格納してないので意味がない?
608~631 if (ShouldPrepareDistanceFieldScene()) DistanceField系(AO、Shadowなど)が使用されているとこのブロックが有効になる
ただし、IntelHD4000シリーズは3Dテクスチャ生成に失敗するらしく、強制的に無効化される
処理内容はDF系技術で必要になるView空間に整列したVolumeテクスチャの作成、更新など
720~727 FGlobalDynamicVertexBuffer::Get().Commit() 動的な頂点バッファとインデックスバッファのコミット処理
主にパーティクル用の頂点、インデックスバッファをアンロック(アンマップ)
732~737 Scene->FXSystem->PreRender() エフェクトの描画事前処理
GPUパーティクルの更新を主に行う
平面リフレクションが有効だったりレンダースレッドが有効な場合はここでは行わない
767~788 RenderPrePass() Z Pre-Pass レンダリングを行う
807 SceneContext.ResolveSceneDepthTexture() Z Pre-Passで描画した深度バッファのリゾル
圧縮深度の展開やMSAAのリゾルブ処理
811 ComputeLightGrid() ライトグリッドの計算
シェーダコードから察するに、Clusterdライティングのためのライトリンクリストを生成している
主に半透明のForward Rendering用
821~826 SceneContext.AllocGBufferTargets() GBufferためのメモリ確保
830~841 RenderOcclusion() オクルージョンに関するレンダリング
低解像度バッファに対するオクルージョンクエリなど
階層型深度バッファもここで作る
Z Pre-Passですべてが深度バッファに描画されている場合に有効になる
SSAOの並列処理を行う場合はここで処理を開始
845~849 RenderShadowDepthMaps() シャドウマップの描画
昔と違ってアトラス化したシャドウマップにレンダリングする
852~857 ClearLPVs() Light Propagation Volumeで使用するバッファをクリアする
859~863 RenderCustomDepthPassAtLocation() CustomDepthをベースパス前に描画する
r.CustomDepth.Orderで0を設定するとこの段階で描画する
DBufferパスで使用することができる
865~868 ComputeVolumetricFog() ボリュームフォグを適用するため、空間分割した3Dテクスチャにライティングを行う
平行光源1灯のみ(多分)、ライトファンクションに対応している
870~878 RenderForwardShadingShadowProjections() Forward Renderingの場合にシーンにシャドウを投影する
Forwardでは複数のシャドウをマテリアル計算時に処理しづらいので、ホワイトバッファという真っ白なバッファに影による減衰を予め計算しておく
885~900 if (bDBuffer) DBufferが有効な場合にDeferred Decalを描画する
902~934 AllocateDeferredShadingPathRenderTargets() Deferred Shading用のレンダーターゲットを確保
条件によってはGBufferや半透明用のボリュームライトバッファもクリアする
936~946 BeginRenderingGBuffer() レンダーターゲットの設定
ワイヤーフレーム表示の場合はMSAAバッファを使用する
951~958 RenderBasePass() ベースパスの描画
複数スレッドで並列にコマンドを積むようになってるっぽい
ベースパス終了後に深度バッファのリゾル
976~985 ClearGBufferAtMaxZ() 描画されなかった部分のGBufferのクリア
通常は必要ないはずだが、GBuffer表示の対応のためか?
987 VisualizeVolumetricLightmap() ボリュームライトマップの可視化
989 ResolveSceneDepthToAuxiliaryTexture() 深度バッファを別バッファにリゾルブする
深度テストしながらフェッチできないハード向け
991~1002 RenderOcclusion() ベースパス後のオクルージョン描画
830行目でレンダリングを行ってない場合はここでレンダリング
1012~1017 RenderShadowDepthMaps() シャドウマップやボリュームフォグの描画
オクルージョン描画がベースパス後の場合はここで行う
1019~1023 RenderCustomDepthPassAtLocation() ベースパス後のカスタムデプス描画
通常はここで描画される
1028~1038 FXSystem->PostRenderOpaque() ベースパス後のエフェクト描画
主に衝突判定をとるGPUパーティクルの更新を行っている
1042~1054 RenderVelocities() 速度バッファを描画する
1057 CopyStencilToLightingChannelTexture() ステンシルバッファに書きこまれたライトチャンネル情報をコピー
1059~1061 GfxWaitForAsyncSSAO() 並列処理しているSSAOのレンダリングを待つ
1065~1081 ProcessAfterBasePass() ベースパス後のポストプロセス
まだ実行されていない場合、Deferred DecalやSSAOの処理を行う
1084~1101 SetRenderTargetsAndClear() ステンシルバッファのみクリア
1111~1114 RenderIndirectCapsuleShadows() カプセルシャドウによる間接照明計算
SceneColorとSSAOに乗算合成される
この段階ではSceneColorはベースパスのエミッシブが書き込まれているはず
1118 RenderDFAOAsIndirectShadowing() DistanceFieldAOの描画
ついでにベントノーマルも計算し、この後数段で使用する
1121~1124 ClearTranslucentVolumeLighting() 半透明用のボリュームライトをクリアする
1127 RenderLights() ライトのレンダリング

ここから RenderLights() 関数内に一旦入ります。
ソースコードは以下です。

Engine/Source/Runtime/Renderer/Private/LightRendering.cpp (316)

行数 コード片 内容
329~332 GatherSimpleLights() パーティクルライトをSimpleLightとして集める
主にタイルベースライティング用
340~365 LightSceneInfo->ShouldRenderLight() 描画すべきライトのリストを作成する
368~375 SortedLights.Sort() シャドウあり、ライトファンクションありなどでライトをソートする
420~424 WaitComputeFence(TranslucencyLightingVolumeClearEndFence) 非同期実行している半透明用ライトボリュームのクリアを待つ
435~455 RenderTiledDeferredLighting() タイルベースのDeferred Lightingを実行する
457~461 RenderSimpleLightsStandardDeferred() SimpleLightをレンダリングする
タイルベースレンダリングを実行している場合はここでのレンダリングは行わない
463~478 RenderLight() シャドウなし、ライトファンクションなしのライトをレンダリング
480~495 InjectTranslucentVolumeLightingArray() シャドウなしライト、SimpleLightを半透明用ライトボリュームに描画する
ジオメトリシェーダを用いて各スライスに1ライト1DrawCallで描画
499~559 UpdateLPVs() LPVが有効な場合、LPVの更新を行う
Reflective Shadow Mapsと直接光をボリュームに流し込む
569 for (int32 LightIndex = AttenuationLightStart;... ここからシャドウあり or/and ライトファンクションありのライトごとの処理
592~631 RenderShadowProjections() ホワイトバッファへシャドウによる減衰を描画する
シャドウはライティング計算時に行うのではなく、一旦フレームバッファと同サイズのホワイトバッファに描き込む
また、半透明用ボリュームライトやハイトマップライトへの描画も行っている
633~637 HeightfieldLightingViewInfo.ComputeLighting() ハイトフィールドのライティング計算
ハイトフィールドだけライティング結果をアトラステクスチャにレンダリングしてる
640~657 RenderPreviewShadowsIndicator() ライトファンクションによる減衰をホワイトバッファに描き込む
659~662 CopyToResolveTarget() ホワイトバッファのリゾル
664~669 InjectTranslucentVolumeLighting() シャドウが有効でない場合はここで半透明用ライトボリュームへレンダリング
674~677 RenderLight() 直接ライトのレンダリング

ライトレンダリングはここまでです。
以降は再び DeferredShadingRenderer.cpp に戻ります。

行数 コード片 内容
1133 InjectAmbientCubemapTranslucentVolumeLighting() Ambient CubeMapを半透明用ライトボリュームへレンダリング
1137 FilterTranslucentVolumeLighting() 半透明用ライトボリュームのフィルタリング
3x3x3のボックスフィルタ
1142~1153 ProcessLpvIndirect() ライティング後のポストプロセス処理
現在はLPVの適用のみ
1155 RenderDynamicSkyLighting() 動的スカイライトの描画
1159 ResolveSceneColor() ここまで描画したSceneColorのターゲットをリゾルブする
通常はRHIのリゾルブ命令を使用するが、モバイルエミュレートの必要がある場合は特殊処理
1162 RenderDeferredReflections() ポストプロセス的に環境リフレクションを適用する
Screen Space Reflectionの計算もここで
1169~1173 ProcessAfterLighting() リフレクション適用後のポストプロセス
現在はScreen Space Subsurface Scatteringのポストプロセス処理のみ
1180~1185 RenderLightShaftOcclusion() ライトシャフトの遮蔽情報の描画
これがなくてもライトシャフトは有効化できるが、カメラ近くに遮蔽物があった場合の品質に差が出る
1188~1212 RenderAtmosphere() Atomospheric Fogの描画
1217~1222 RenderFog() Height Fogの描画
ボリュームフォグもここで適用される
1224~1236 RenderPostOpaqueExtensions() 不透明描画後の追加処理
ユーザがC++で指定できるレンダリング処理っぽい?
ComputeShaderのDispatchもできるようだ
1252~1261 RenderTranslucency() 半透明オブジェクトの描画
Separate Translucencyの場合は別バッファに描画して合成まで行う
1267~1275 RenderDistortion() Refractionが有効なマテリアルを描画
1280~1286 RenderLightShaftBloom() ライトシャフトを描画する
遮蔽情報が描画されているかどうかで結果が異なる
1288~1293 RenderOverlayExtensions() 多分ユーザが実行可能なレンダリングパス
RenderPostOpaqueExtensionsと同様
1295~1303 RenderDistanceFieldLighting() DistanceField系のライティング処理(GIなど)
1306~1318 RenderMeshDistanceFieldVisualization()
RenderStationaryLightOverlap()
デバッグ用の情報ビジュアライズ
メッシュDFとStationaryLightのオーバーラップ情報
1336~1341 GPostProcessing.Process() ポストプロセスの描画
後述
1361~1366 RenderFinish() 描画の終了処理
デバッグビジュアライズなど

以上です。
ここからはポストプロセスです。
Engine/Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp (1262) この場所からです。

行数 コード片 内容
1385~1451 AddPostProcessDepthOfFieldGaussian()
AddPostProcessDepthOfFieldCircle()
AddPostProcessDepthOfFieldBokeh()
被写界深度(DoF)の描画
BokehDoFの場合はSeparate Translucencyバッファとの合成もここで行う
1453~1463 RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOFRecombine(bIsComputePass)) BokehDoFではない場合はここでSeparate Translucencyを合成する
1465 AddPostProcessMaterial(Context, BL_BeforeTonemapping, SeparateTranslucency) トーンマップ前のポストプロセスマテリアルを適用する
1469~1482 AddTemporalAA() TemporalAAを適用する
1484~1544 FRCPassPostProcessMotionBlur() モーションブラーを適用する
[A Reconstruction Filter for Plausible Motion Blur]を改良したものを利用
スケール値を変更した2パス描画によってより高クオリティのブラーも適用できる
r.MotionBlurSeparable に 1 を指定すれば有効になる
1546~1559 FRCPassPostProcessVisualizeMotionBlur() モーションブラーとブルームのビジュアライズ
1562~1572 FRCPassPostProcessDownsample() SceneColorバッファを半解像度バッファにダウンサンプリング
1574~1609 FRCPassPostProcessHistogram() ダウンサンプルしたSceneColorを利用し、画面のヒストグラムを求める
1612~1625 CreateDownSampleArray() ブルーム用に半解像度SceneColorから複数回のダウンサンプリングを行う
Eye Adaptationが有効な場合はこの中でセットアップを行う
1628~1653 AddPostProcessBasicEyeAdaptation()
AddPostProcessHistogramEyeAdaptation
Eye Adaptationを実行する
ブルームが無効の場合はここでダウンサンプルを行う
1655~1756 AddBloom() ブルームを適用する
レンズフレア、レンズブラーもこの関数内で行う
1760~1790 AddTonemapper() トーンマップを適用する
1792~1795 AddPostProcessAA() FXAAを適用する
1797~1804 FRCPassPostProcessVisualizeDOF() DoFのビジュアライズ
1808~1823 AddGammaOnlyTonemapper() フルポストプロセスでない場合、Separate Translucencyの合成と簡易トーンマッパーを適用する
1826~1906 FRCPassPostProcessVisualizeComplexity()など 各種ビジュアライズなど
1908 AddPostProcessMaterial(Context, BL_AfterTonemapping... トーンマップ後のポストプロセスマテリアルを適用する
1910~1982 FRCPassPostProcessSubsurfaceVisualize()など 各種ビジュアライズなど
1986~2025 FRCPassPostProcessUpscale() スクリーンパーセンテージが有効な場合はここでアップスケール

以上となります。
おつかれさまでした。

通常、UE4を使う場合はこれらのパスの順序を意識する必要もないのですが、どうしても処理を入れ替えたい、無駄な処理を省きたいなどの場合にはソースコードを読まなければなりません。
本記事がそのような方の一助になれば幸いです。

明日は @negimochi さんで、「Sequencer 構造解説とカスタムトラック追加 (UE4.18版)」です。