3Dノイズに疑似的なポジションを提供する

Twitterでも呟いてたネタをもう少し詳しく。
以前のブログ記事にも書きましたが、Substance Designer/Painterに3Dノイズが存在します。
3Dノイズは3次元空間の座標に対して連続的なノイズを生成してくれる代物ですが、逆を言えば3次元空間の座標が存在しなければノイズを生成してくれません。

Substance Painterは必ずメッシュを読み込まなければならないため、3次元空間の座標は簡単に取得できますが、Substance Designerでは必ずしもメッシュが読み込まれるとは限りません。
では、SDでメッシュが読み込まれていなければ使い道がないのか?というと、実はそんなこともなかったりします。
ポジションマップに相当する画像があれば使えるのです。
というわけで、今回はハイトマップを元に疑似的なポジションマップを作成し、そこから3Dノイズを使うというのをやってみます。

まず、[Pixel Processor]を用意しましょう。
次に[Pixel Processor]内部に入り、こんな感じにノードを組んでみます。

f:id:monsho:20180523230741p:plain

[Get Float2]で $pos を取得すると、0~1までのUV値を取得できます。
つまり、この[Pixel Processor]はRGチャンネルにUV値が、Bチャンネルに0が、Aチャンネルに1が入った状態になります。
この物体が3次元空間中に存在するとすれば、縦横の幅が1で、XY平面に水平な板ということになります。
では、この出力結果を適当な3Dノイズに接続してみましょう。

f:id:monsho:20180523231319p:plain

このようにちゃんとしたノイズが生成されているのがわかります。
ただ、2D Viewを見ているとわかると思いますが、ちょっとジャギジャギしてます。
理由は簡単で、[Pixel Processor]の出力が8ビットなので、3D座標の精度が低くなってしまっているのです。
なので、[Pixel Processor]のフォーマットを変更しましょう。とりあえず16ビットでOKです。

f:id:monsho:20180523231802p:plain

一目瞭然ですね。

次に、グラフのパラメータにFloatパラメータを追加しましょう。IDはheightとしておきます。
そして、[Pixel Processor]を以下のように変更します。

f:id:monsho:20180523232227p:plain

Bチャンネルに入る値が0ではなくheightというパラメータに変わりました。
あとはheightパラメータをいじってみましょう。ノイズが連続的に変化していくのがわかると思います。

では最後に、[Pixel Processor]を以下のように変更しましょう。

f:id:monsho:20180523232414p:plain

入力されたグレースケール画像を高さ(Z方向)とみなして疑似的なポジションを生成します。
結果はこんな感じになります。

f:id:monsho:20180523233000p:plain

使い物になるのか?というとなかなか難しい部分はあるかもしれませんが、今までにないようなノイズの生成が可能になるかもしれません。
興味がある方は実際に作ってみて、いろんなハイトマップを入力してみましょう!

.sbsファイルをコピーする際の注意点

Substance Designerゆるゆる会にて、こんな感じのエラーが出るというハプニングが。

f:id:monsho:20180423013344p:plain

とある. sbs ファイルを開いたときに出てきたエラーなのですが、エラーメッセージを見ての通り、開いた.sbsファイル内で使用されている. sbs ファイル(このエラーメッセージの場合は grunge_map_010_bitmap.sbs)が見つからないというものです。

このようなエラーは .sbs ファイルをよその環境などにコピーした際に発生します。
自身のライブラリに存在するノードを使用したが、コピーした先の環境にはその .sbs が存在しないとか、存在はしてもパスが通ってないとかの理由からファイルを見つけることができず失敗します。

新しい環境にもそのファイルが存在するのであれば、上の画像のダイアログで [Yes] を選択したあと、ファイル選択ダイアログが表示されるので、そこから対象となる .sbs ファイルを設定すればいいのです。

しかし、コピーするたびにパスを変更しなければならず、同じ環境しか使わないならともかく複数環境を行ったり来たりする場合には大変面倒です。

そこで今回紹介するのが .sbs ファイルを依存ノードを含んでエクスポートする方法です。
勉強会などに参加する場合には参考にしてください。

やり方はとっても簡単。
エクスポートしたい .sbs をSubstance Designerに読み込ませ、エクスポートしたい .sbs を右クリック、[Export with dependency] を選択しましょう。

f:id:monsho:20180423014727p:plain

以下のようなウィンドウが出てきたらエクスポートするフォルダを選択してOKをクリックするだけです。

f:id:monsho:20180423020952p:plain

最終的にはこのように、エクスポート対象と .sbs ファイルと、dependencies フォルダに依存するファイルがエクスポートされます。

f:id:monsho:20180423021137p:plain

以上です。

Subsntace Designerでカラーを設定するパラメータあれこれ

Substance Designerでマテリアルを作成した場合、ベースカラーを変更したいと考えることが多いと思います。
例えばタイルの色を白や黒、赤や青に変えたい場合が多くあるでしょう。
このような場合のカラーを変更する手段を3つほど紹介します。

HSL

HSLノードはHSL色空間を利用して色を変化させるときに使用します。
HSL色空間はHSV色空間と似たものですが、ちょっと計算式が違っています。
色相を360度の角度で表すため、赤→青と変化させる際に彩度と輝度は変更したくない場合、色相は変えずに彩度や輝度だけを変更したい場合に使いやすいです。
使い方も簡単で、出力するベースカラーに挟むだけでOKです。

f:id:monsho:20180326164818p:plain

上の画像では色相のみを変更して紫色にしていますので、彩度と輝度は維持されます。
パラメータはHue(色相)、Saturate(彩度)、Lightness(輝度)の3つがあり、パラメータをExposeする場合はすべてのパラメータをそのままExposeするだけでOKなので簡単です。

Replace Color

ある特定の色を別の色に変化させる場合に必要なHSLの変化量を自動計算してくれるノードです。
パラメータはSource ColorとTarget Colorの2つがあり、ユーザはSource Colorに代表色を設定しておきます。

f:id:monsho:20180326165941p:plain

変更したい色を出力するノードのすぐ後ろに接続し、以下の手順でパラメータを設定します。

f:id:monsho:20180326170242p:plain f:id:monsho:20180326170421p:plain

この後、Target ColorをExposeすればOKです。

若干面倒な部分としては、出力したいカラーのデフォルトが変更した場合にSource ColorとExpose済みのTarget Colorの両方を変更しなければならないという点です。
Source Colorは問題ないのですが、Expose済みのTarget Colorのデフォルト値を変更するのは上記の手段ではできません。
Source ColorをコピーしてTarget Colorに設定するか、Exposeしたパラメータを削除してもう一度同じ手順を踏むかのどちらかとなります。

Gradient (Dynamic)

上記2つの手法は全体の色を変化させることはできますが、Gradient Mapノードで作成したグラデーションの変化量などは変更できません。
これらを変更させたい場合も多分にあるのではないかと思うのですが、Gradient Mapのグラデーションは残念ながらExpose可能なパラメータではありません。
Gradient (Dynamic)ノードを用いると入力マップとしてGradient Mapの結果を入力することができます。

f:id:monsho:20180326171913p:plain

このノードの組み方はデフォルトのグラデーションが存在し、それとは別のグラデーションを使いたい場合の設定も可能にしたバージョンです。
もし、必ずユーザにグラデーションを設定させるのであれば、下のGradient (Dynamic)の出力を用いればOKです。
上記の組み方の場合は設定項目が多めになりますので注意してください。
手順はこのようになります。

f:id:monsho:20180326172532p:plain f:id:monsho:20180326172752p:plain f:id:monsho:20180326173653p:plain f:id:monsho:20180326173515p:plain f:id:monsho:20180326174015p:plain

画像がないとないとわかりにくかったので説明も画像に入れちゃいましたが、検索性が悪くなるんだよなぁ…

とりあえず自分がよく使う手法は以上ですが、他にこの方法いいよ!ってのがあったら教えてください。

Substance 2018の3Dノイズについて

Spring has come!

は~るで~すよ~(弾幕
というわけで暖かくなって花粉が飛びかう春が来ましたね。

春といえば!
Substance Designer/Painterのアップデートです!
というわけで双方とも2018.1が来ていますので、サクッとアップデートしちゃいましょう。
まあ、不具合はあるかもしれませんがね。

今回のアップデートではパフォーマンス向上が結構重要です。
特にSubstance Painterではシェルフのアイテム読み込みが驚くほどに速くなってます。
さすがに一瞬ということはありませんが、今までのように下手すると数分待たされるということはなくなりました。
特にSPで作業をする人にとっては朗報じゃないでしょうか。
また、UIもかなり変わっていますね。

Substance DesignerはUDIMのベイクに対応しています。
パフォーマンス向上はランダムシードの変更時にわかりやすいと思います。

さて、双方に共通しているアップデート内容として3Dノイズの追加が挙げられます。
この3Dノイズはかなりの可能性を秘めていると個人的には思うので、少し詳しく書きます。

プロシージャルにおけるノイズ

ノイズという単語は音楽の中に入ってくるプツプツといったもの、雨音のようなものなどを指す言葉として使われるのが一般じゃないかと思います。
映像系であれば8mmフィルム特有の線が入ったりとか、写真の場合はISO感度を高くしすぎた場合のざらつきなどでしょうか。

プロシージャルにおけるノイズは非常に重要で、SDならプロシージャルマテリアルの起点、ディテール作成など様々な分野で活躍します。
これらのノイズは完全に不規則なものではなく、ある程度の規則性を持ったランダムな画像になります。
ランダムなのに規則性とはこれいかに?という疑問もあるかもしれませんが、Perlin Noiseなどは特にわかりやすいのではないかと思います。

f:id:monsho:20180317010737p:plain

白と黒を凹凸と考えると、凹凸の場所自体に規則性はないものの、凹凸への変化は滑らかです。
SDのノイズのほとんどは規則性を持ったランダムノイズなので、タイリングも正しく行われますし、それらを如何様にブレンドしてもタイリングの原則は確保されるという便利な代物です。

しかし、これらのノイズ画像は2Dの画像ですので、平面ではその恩恵を受けられますが、3Dモデルに適用しようとすると問題が出てきてしまうのです。

3D空間で2Dノイズを使用する

手始めに球を出してみます。UV展開は以下のようになっています。

f:id:monsho:20180317011408p:plain

これに通常のノイズ代表として、[Cells 1]を貼りつけてみましょう。

f:id:monsho:20180317011553p:plain

2Dビューポートを見ると規則性のあるランダムになっていることがわかりますね。
しかし3Dビューポートは一律にランダムが適用されているように見えません。
中心部の胴体部分はノイズが横に伸びているように見えますし、球の頭頂部などはひどい有様です。

f:id:monsho:20180317011835p:plain

これを避ける方法として Tri-planar で貼りつける方法もあります。
Fill Layer のプロパティで Projection の項目を Tri-planar projection を選びます。

f:id:monsho:20180317012438p:plain

結果はこのようになります。

f:id:monsho:20180317012605p:plain

一見するとうまくいっているように見えますが、中心付近からY字型にラインが入っているように見えませんか?
これは Tri-planar の特徴です。

UVマップでのテクスチャ貼りつけは、1枚のテクスチャで球を包み込むような形になりますが、Tri-planar はXYZそれぞれの軸に垂直に6枚のテクスチャを貼りつけるようにします。
それぞれの軸に向いた面に対しては正しく貼りつけられるのですが、中間部分(斜め45度向き)では複数軸から貼りつけた内容をブレンドすることになります。
上の画像のY字型のラインはまさにこのブレンドされている部分です。
Tri-planar はある程度のクオリティは確保してくれるので使い物にならないわけではないのですが、連続性が微妙な部分が出てきてしまうのは避けられません。

3Dノイズを使用する

3Dノイズは2D空間にしか適用されなかったノイズを3D空間に拡張したものです。
つまり、奥行き方向にも連続性を持ったランダムが適用されるというわけです。

SPではシェルフ内の [Procedurals] カテゴリにまとめられています。

f:id:monsho:20180317015022p:plain

これをFill Layerに適用してみましょう。

f:id:monsho:20180317015115p:plain

Tri-planar の時と同じ場所をスクリーンショット撮影しているのですが、Y字のラインは見当たりませんね。
これは3Dノイズが3次元的な連続性を持っているからであり、連続性を持った面に対して連続性持ったノイズを提供してくれているのです。

ただし1点だけ注意を。
3Dノイズを使用する場合、ノイズの参照データとして Position Map が必要になります。
こいつはちゃんとベイクするようにしましょう。
SDで使う場合もメッシュを読み込んでその Position Map を生成しないと役に立ちません。

より自由にハイトマップを作成する

Substance Designerでは最初にハイトマップを作成するのが基本的なアプローチになります。
しかし、何も考えずにハイトマップを作っていくと数値が飽和してノーマルが残念なことになったりします。

f:id:monsho:20180309001758p:plain

[Blend]ノードでは加算(Add)を使っています。
この場合、[Shape]ノードの段階で数値が 1.0 のピクセル、つまり画像中心のピクセルは2倍されるので2.0になるはずです。
そしてそこからすそ野に向かって徐々に数値を減らしていくはずなので、生成されるノーマルマップは山なりになっているべきなのですが、上の画像のように途中から平べったくなってしまいます。

なぜこのようになるのかというと、基本的なフォーマットでは数値が1.0までで飽和してしまい、それより大きな値は1.0にクランプされてしまうからです。
実際に3DViewで高さとノーマルに上の画像のノードを割り当てるとこのようになります。

f:id:monsho:20180309002803p:plain

高さが途中でクランプされてしまっているのが一目瞭然です。

これを避けるため、通常は高さが1.0を超えない範囲でうまくやるものなのですが、[Blend]ノードによる加算や Add Sub などを使ってしまうと意図せず1.0を超えてしまうこともあります。
1.0を超えてしまった部分はノーマルマップが平坦になってしまうため、大変見苦しい結果となってしまいます。

f:id:monsho:20180309003326p:plain

このような画像になるのは意図していない場合がほとんどですが、ユーザが変更可能なパラメータが多くなってくると、パラメータの組み合わせによってはこのような結果となってしまうことが多々あります。

これを避ける方法の1つが浮動小数点フォーマットを使用する方法です。

f:id:monsho:20180309003942p:plain

浮動小数点フォーマットを利用すると、ノードの数値は 1.0 より大きな値を取り扱うことが可能になります。
そのため、最初の画像は以下のような結果となります。

f:id:monsho:20180309004156p:plain

中心部分の高さがクランプされず、滑らかな形状ができていることが確認できますね。

ただし、どこの段階で浮動小数点マップを使用するか、どの段階で16ビットフォーマットに変換するかは難しい判断となります。

例えば、[Gradient Map]ノードのように、入力されて入ってくる数値が 0.0~1.0 の範囲になっていることを前提としているノードでは、1.0を超えた値は1.0と同じ結果となります。

また、[Slope Blur]などのノードに浮動小数点フォーマットのノードを使うと、意図しないノイズが出てしまったりします。

f:id:monsho:20180309005821p:plain

左の画像は[Slope Blur]を使用する段階で浮動小数点フォーマットに変換した場合ですが、細かなノイズが表面に出てしまっているのがわかります。
それに対して、ハイトをノーマル生成ノードに渡す部分で浮動小数点フォーマットに変換した場合は滑らかな表面になっています。
浮動小数点フォーマットは速度面にも問題が出る可能性が高いので、ハイトをブレンドする場所で主に使うようにし、そこを超えた段階で [Auto Levels] ノードを使って 0.0~1.0 の範囲に収めるようにするのが良いのではないかと思います。

Substance Designerのノード構成覚書

これから少しずつですが、Substance Designerでこういうノード構成するとこういうのが作れます、というのを覚書的に書いていこうと思います。

内臓的な形状

内臓のような有機的な形状を作るノード構成です。
これはTwitterでも呟きましたが、ここにも再掲。
基本は[Perlin Noise]と[Slope Blur Grayscale]を用います。

f:id:monsho:20180226224932p:plain

[Slope Blur Grayscale]の Grayscale 入力と Slope 入力の両方に[Perlin Noise]を繋ぎます。
後者のように Slope に繋ぐ前にブラーをかけても良いですね。
後者の場合はより滑らかな感じになります。

[Slope Blur Grayscale]の Mode パラメータは Blur か Max にしましょう。Min だとうまくいきません。
Samples パラメータは基本最大、Intensity は Slope に繋げているノードによってうまいパラメータは変化しますので調整してみてください。
なお、後者のパラメータはこんな感じ。

パラメータ名 数値
[Blur HQ Grayscale]のIntensity 14
[Slope Blur Grayscale]のIntensity 4

ノイズは他のノイズでも使えるものがありますが、どんなノイズでもうまくいくわけではないので注意してください。
とは言え、いろいろなノイズで試してみると面白いと思います。
一例として、[Grunge Map 002]、[Crystal 2]、[Liquid]、[Moisture Noise]で試したものを掲載します。

f:id:monsho:20180226231416p:plain

物によっては布っぽい形状も作れますね。

直線と曲線を織り交ぜた形状

石畳のようなマテリアルを作る際、スタイライズドなマテリアルの場合は形状が結構難しかったりしませんか?
直線と曲線を織り交ぜたような、単純に歪んでるだけじゃない、手書きにあるような意図的な飛び出し部分などが欲しい場合があるかと思います。

石畳の基本形状は [Tile Random] と [Distance] を使うことが自分は多いですが(クラックの作成とかでも使う)、この方法では形状が直線的になりすぎます。
そこで、そのあとに [Perlin Noise] と [Warp] で歪ませたりするのですが、これだと歪みが曲線的過ぎてイメージが何となく違います。

そこで考えたのが以下の2種類の方法です。

[Crystal 1] と [Directional Warp] を使う方法

f:id:monsho:20180227000702p:plain

[Distance] の後に [Directional Warp] と [Crystal 1] を使って、尖った歪みを作成する方法です。
[Perlin Noise] で歪ませるよりは直線的な歪みができます。
しかし、あまり曲線的な部分は出てこないので、[Perlin Noise] の歪みと併用の方がいいかもしれません。
結果は以下のようになりました。

f:id:monsho:20180227001754p:plain

[Distance] の前に歪ませる方法

つい最近、適当に接続したノードが割といい感じになっていたので、それをもとに作ってみたものです。
[Tile Random] から [Distance] に繋げる前に [Warp] と [Perlin Noise] で歪ませます。

f:id:monsho:20180227001931p:plain

歪ませた後に [Edge Detect] でエッジ抽出し、それを [Blend] の Subtract で減算します。
これは [Warp] 後にエッジ部分にアンチエイリアス的に幾分滑らかになってしまうためです。
この状態で [Distance] に通すとうまく形状に合わせて広がってくれません。
後半部は前述の方法と同じなので割愛。
そして結果は以下。

f:id:monsho:20180227002243p:plain

どちらがいいかと言われると好みの問題かな、とも思います。

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版)」です。