【UE5】UserSceneTextureを使ってガウシアンぼかしを作ってみた – Unreal Engine 5.5

Unreal Engine

水ノ茉の宣伝

準備中...
ゲームを作る予定なの
水ノ茉こおり

作業環境

  • Windows 10
  • Unreal Engine 5.5 (59a9aa7)
  • Visual Studio 2022 (17.10.4)
  • Visual Studio Code

注意点

本記事は Unreal Engine 5.5 が正式にリリースされる前のブランチ (hash: 59a9aa7) を元に作成した内容です。
正式リリースとは異なる情報が含まれている可能性があります。

始まり

先日情報公開されたのを機に Unreal Engine 5.5 のロードマップを読んでいたところ気になる内容を見つけました。

Render Pass Improvements

In Release 5.5 it is now possible to author scene captures and custom render passes which automatically mirror the main view’s camera and resolution, including temporal AA jitter and screen percentage. This allows compositing layers to be pixel identical, and compositing in general to take advantage of screen percentage for better performance.

Additionally, a user defined intermediate texture, dubbed “UserSceneTexture” can be written to and read from sequentially as a post process material. This means that UE now supports running multi-pass post process effects (such as separable filters or blurs) in a more performant manner.

https://portal.productboard.com/epicgames/1-unreal-engine-public-roadmap/c/1666-render-pass-improvements

読めないので翻訳さん。

\ 中 間 テ ク ス チ ャ /

そう、ついにポストプロセスマテリアルに中間テクスチャが実装されました。

従来であればCanvasやRenderTextureを用いたり、FSceneViewExtensionBaseを使用したり、エンジン改造をしたり・・・

Unityなら当然の如く扱えた中間テクスチャというありふれた行為にバカみたいなコストを払う必要がありました。

ついにその呪縛から解放されます。

そしてなんと中間テクスチャの他にダウンサンプルも実装されていました。

「Render Pass Improvements」の説明にも登場している「separable filters」などのぼかしフィルタは、計算負荷が高いので負荷軽減としてダウンサンプルを100%と言っていいほど組み込みます。

故に最初この内容を見た時は「中間テクスチャだけあっても結局は負荷軽減でダウンサンプル自前でやるしかねぇよなぁ」とテンションがジェットコースターでしたが、いざ実装を見たら大歓喜です。

前回の5.4もポストプロセスに機能面で良い変更点が入っていたので2連続で嬉しいアプデかもです。

破壊的変更に関しては最早気にしないことにしています。
これに関してはエンジン改造を武器に振り回している身分、受け入れるしかねぇと悟りました。

実際にぼかしフィルタを作りながら使い勝手と実装を確認していきましょうか。
実装に関しては個人的に掌握したいだけなので、メモ帳程度です。

Visual Studio Installer

ソリューションを立ち上げて暫く待っていると警告が出力されることがあります。

大体は要求コンポーネントが不足している内容です。
案内に従って不足しているものをインストールしましょう。

面倒くさがってサボるとエンジンビルド失敗します。たぶん。

あとついでに初回ビルド時は、リビルドじゃなくて、ビルド操作を推奨します。
改善されていなければ初手リビルドするとコピーするファイルが存在しなくて失敗するという問題があります。

警告が出力された場合は案内に従ってインストール
バージョンアップ作業で見慣れたビルド ツールの更新
アプデを重ねるたびに増加するビルド時間

User Scene Texture と User Texture Divisor の実装確認

中間テクスチャの機能名はUserSceneTextureです。

UserSceneTexture

最初はSceneTextureから選択するスタイルと思っていましたが別ノードの様ですね。

SceneTextureはこれ単体でバリエーション制御とかしているので確かに別ノードで実装した方が内部的にも素直かもですね。

リストボックスを非採用が故に任意のバッファ名でやり取りできる点は寧ろ扱いやすいかも。

SceneTextureから選択するわけではないらしい

UsedSceneTexturesのビット演算用の型はまだ32ビットなのか。

マテリアルインプットなどのビット演算で使用されていた型たちは、5.2から5.3のタイミングで64ビットに格上げされたので、それに倣って随時されると思ったのですが、まだですか・・・

SceneTextureから取得できる要素を増やす際には当然、ESceneTextureId列挙体にも追加するんですが、32以上だとビット演算が死んじゃうので毎回64ビットに拡張対応しているので、そろそろ標準で64にして欲しいな。

PPI_Anisotropyが既に30だから割とすぐ超過しちゃうのよね。

地味にビット演算でゼロに戻ってくるだけだからビルドエラーにならない初見殺しポイントなのよ。

ここは引き続き拡張する時の注意点っと。

ほぇー。FMinimalSceneTexturesがUserSceneTextureのFRDGTextureRefを所有してるんだ。

mutable付与されてるわ。

FSceneTexturesはあっちこっちで使われているから全対応面倒だったのかな。

というかポストプロセス階層から随分と旅立ったな。

最初は違和感あったけど、SceneCaptureのRTに対応してるからこの場所でも妥当か。

ポストプロセスの機能の割に400行のヒット数は異常だと思ったけど、SceneCaptureを考えたらそんなもんでしたね。

FSceneTexturesに入ってるならエンジン改造との親和性も高いのでぱっと見は優等生かも。

ダウンサンプルは予想通りGetDownscaledViewRectにぶち込んでるだけですね。

本当にUtils関数群便利過ぎる。

今後改造でお付き合いすることになる部分は大体把握できたので使っていきましょうか。

ガウシアンぼかしとは

ぼかしフィルタのひとつです。

真面目な説明はググればヒットするので割愛します。

ぼかしなし
ぼかしあり

パス構成

作成するのは1パスなぼかしフィルタではなく、2パスなぼかしフィルタです。
冒頭で触れた「separable filters」というやつですね。

ガウシアンぼかしの最適化手法として一般的だと思うので係数まわりの説明は割愛します。
というのは建前で、数字について正確に説明するほどの技量がないです。

パスの流れとしてはこんな感じです。
左が入力テクスチャ、右が出力テクスチャです。

1パス目の結果を中間テクスチャに書き込む感じですね。
説明のために2パス目の結果も中間テクスチャに書き込むので、最終的なコンポジットを含めると3パス構成となります。

シーンカラーに直書きでも実装は出来ますが、一般的なアプローチが中間テクスチャを用いるという感じです。
ぼかしパスについては中間テクスチャがないと破綻するような例が特に思い付かなった。
特定オブジェクトだけをぼかす場合でもシーンカラー直書きで確か出来るし。

特定のオブジェクトをぼかす

水平ぼかしを作る

1パス目の水平ぼかしを作っていきます。

ポストプロセスマテリアルを作る

マテリアルを作成
Post Process Materialの頭文字を取ってPPM_GaussianBlurHorizontal
PPM_GaussianBlurHorizontalを開いてMaterial DomainをPost Processに変更
Blendable Location から Scene Color Before DOF を選択

マテリアルパラメータコレクションを作る

水平と垂直、異なるポストプロセスマテリアルで共通のパラメータ制御をするためにMPCを使用します。

Material Parameter Collectionを作成
Material Parameter Collectionの頭文字を取ってMPC_GaussianBlur
MPC_GaussianBlurを開いてScalar Parametersの横の+ボタンを3回クリック
パラメータ名とデフォルト値を設定
Enabledがぼかしの有効性、Kernelがぼかし幅、Sigmaがぼかし度合い

カスタムノードで組んでいく

こういう感じのノードを組んでいきます。

SceneTexture

SceneTextureノードを配置
Scene Texture Id から PostProcessInput0 を選択

TextureCoordinate

TextureCoordinateノードを配置

CollectionParameter

CollectionParameterノードを配置
Collection から MPC_GaussianBlur を選択
Parameter Name から Enabled を選択
Kernel と Sigma も配置

Custom

Customノードを配置
Inputs の右側にある+ボタンを5回連打
Input Name を上から SceneColor, InvSize, UV, Enabled, InKernel, Sigma と入力
Additional Defines の右横の+ボタンをワンポチ
Define Name に HORIZONTAL_PASS を設定
Define Value はなにも入力しないでおっけい
入出力ピンを接続

カスタムノードを書く

水平と垂直の両方に対応したコードを書いていきます。

コピペ

Codeにコピペ

コード全文

#ifdef HORIZONTAL_PASS
#define PPI_TargetSceneTextureId PPI_PostProcessInput0
#else
#define PPI_TargetSceneTextureId PPI_UserSceneTexture0
#endif

#define CalcWeight(Value, Sigma) (1.0 / (sqrt(PI * 2.0) * Sigma) * exp(-Pow2(Value) / (2.0 * Pow2(Sigma))))

BRANCH
if (Enabled <= 0.0)
{
    return SceneColor.rgb;
}

float  Weight = CalcWeight(0.0, Sigma);
float3 Color = SceneColor.rgb * Weight;

const uint Kernel = max(1, round(InKernel));

LOOP
for (uint i = 1; i < Kernel; i++)
{
    float4 NewUV;
#ifdef HORIZONTAL_PASS
    NewUV.xy = UV + float2( float(i), 0.0) * InvSize;
    NewUV.zw = UV + float2(-float(i), 0.0) * InvSize;
#else
    NewUV.xy = UV + float2(0.0,  float(i)) * InvSize;
    NewUV.zw = UV + float2(0.0, -float(i)) * InvSize;
#endif

    float4 NewClampedUV;
    NewClampedUV.xy = ClampSceneTextureUV(ViewportUVToSceneTextureUV(NewUV.xy, PPI_TargetSceneTextureId), PPI_TargetSceneTextureId);
    NewClampedUV.zw = ClampSceneTextureUV(ViewportUVToSceneTextureUV(NewUV.zw, PPI_TargetSceneTextureId), PPI_TargetSceneTextureId);

    float NewWeight = CalcWeight(float(i), Sigma);

    Color += SceneTextureLookup(NewClampedUV.xy, PPI_TargetSceneTextureId, 0).rgb * NewWeight;
    Color += SceneTextureLookup(NewClampedUV.zw, PPI_TargetSceneTextureId, 0).rgb * NewWeight;
    Weight += NewWeight + NewWeight;
}

return Color * (1.0 / Weight);

水平と垂直のバリエーション

#define HORIZONTAL_PASSが存在する場合は水平方向にぼかす処理になり、存在しない場合は垂直方向にぼかす処理が採用されるようになっています。

Additional DefinesでDefine Nameを入力しただけで数値未指定の理由はこういうことだったのです。

ifndef運用が好きじゃない人は自由にEqualに変えてもらっても良きです。

CalcWeightは本当ならushに関数を用意したかったのですがファイルの作成が面倒だったのでマクロで雑に解決です。

心配性の方はSigmaにmax(0.0001, Sigma)を付けてください。
ゼロ値を指定されるとゼロ除算で動作が担保されません。

ViewportUVToSceneTextureUV, ClampSceneTextureUV, SceneTextureLookup

関数と機能説明はちょっと長いので過去の記事を参照してください。

Blendable Location はこっち。

ポストプロセスマテリアルをセット

Outliner から PostProcessVolume を選択
Rendering Features > Post Process Materials > Array の右横の+ボタン
Choose から Asset reference を選択
PPM_GaussianBlurHorizontal をセット

見た目を確認する

ぼかし度合い
ぼかし幅

書き込み先をシーンカラーから中間テクスチャに変更する

PPM_GaussianBlurHorizontal を開いて User Scene Texture に Blur (Horizontal) と入力

User Scene Textureに文字列を指定することでそのポストプロセスマテリアルのRenderTargetはSceneColorから中間テクスチャに切り替わります。

この状態で Apply か Save this asset をすると先ほどまで確認できた水平ぼかしが見れなくなり、代わりにデバッグ情報が表示されます。

DebugPrintは作成手順が整備されているとはいえ、可視化しないと把握が面倒な部分を整備してくれたのは地味に嬉しいですね。

水平ぼかしが反映されずにデバッグ情報が表示される

あとついでに、露出は重ね掛けする必要が無いのでDisable Pre Exposure Scaleにチェックを入れて切ってください。

Disable Pre Exposure Scale にチェックを入れる

垂直ぼかしを作る

2パス目の垂直ぼかしを作っていきます。

基本的には水平ぼかしで作成した内容のコピペです。
だから垂直だけ作りたい場合でも水平の手順を踏んで下さいね。
そうしないと内容スッカスカ。

ポストプロセスマテリアルを作る

PPM_GaussianBlurHorizontal をコピペする
コピペした方の名前を PPM_GaussianBlurVertical に変更
User Scene Texture と Disable Pre Exposure Scale を初期値に戻す

カスタムノードを組む

こういう感じのノードを組んでいきます。

UserSceneTexture

UserSceneTextureノードを配置
User Scene Texture に Blur (Horizontal) と入力

Custom

Additional Defines の右横のゴミ箱ボタンをクリック
SceneTextureノードを削除して、UserSceneTextureノードで再接続

カスタムノードを書く―――ことないか

ただの見出しです。

PPI_UserSceneTexture0

ノード的にはSceneTextureとUserSceneTextureで分かれていますが、内部で呼ばれている関数は同様にSceneTextureLookupです。

そしてPPI_XXXも名称がPPI_PostProcessInput0とPPI_UserSceneTexture0で違いますが、これまた内部的には#define PPI_UserSceneTexture0 PPI_PostProcessInput0となっているのでHLSLコードだけで見れば特段変わったことはないですね。

中間テクスチャとシーンカラーの両方をフェッチしている場合は、その限りではないかもですが。

ポストプロセスマテリアルをセット

先ほどと同じように PPM_GaussianBlurVertical をセット

できた

飽きてきた

ダウンサンプルしてみる

解像度を半分にしてみます。

そして飽きてきたのでざっくりになります。

PPM_GaussianBlurHorizontal を開いて User Texture Divisor に 2, 2 を入力
PPM_GaussianBlurVertical を開いて User Scene Texture に Blur (Vertical) を入力
Resolution Relative To Input に Blur (Horizontal) を入力
両マテリアルを保存か適用するとぼかしが消失してデバッグ表示に切り替わる
新しいポストプロセスマテリアル PPM_GaussianBlur を作成
Blendable Location は Scene Color Before DOF を選択
UserSceneTextureノードを配置
User Scene Texture に Blur (Vertical) と入力
ピンを接続
PPM_GaussianBlur をセット
フル解像度
ハーフ解像度

User Texture Divisor

User Texture Divisor が除算値を入力するところです。

Resolution Relative To Input

ここに中間テクスチャの名前を入力すると、出力サイズが指定された中間テクスチャサイズと同様になります。

上の例だと Blur (Horizontal) がハーフ解像度なので Blur (Vertical) の出力も同様のサイズになる感じです。

最終的な出力はシーンカラーサイズにまでアップサンプルしないといけないので、新しく最終出力的なポストプロセスマテリアルを作った感じでした。

おわり!!!

あとは深度と速度を書き込めるようにしてTSR対策を標準で出来れば割と文句ないかもですね。

試しにキャラ以外をぼかしてみた
やり過ぎると浮くんだよね

共通ルート編

シークレットラブ(仮)のネタバレを含みます。

名取さん随分と可愛いキャラやなぁ。

癒されるわ。

別に……いいですよ。と言ってる割にはめっちゃ嫌そうで草。

マウント合戦、おもろい。

なるほどね、これで冒頭のシーンに戻るのね。

若干気になるのは楓さんの取り巻きはこの状況に立ち会っているのかね。

先に帰らしたのかね。

クレープ持ってる楓さんかわいい!

楓さんキスしてる!!

かわい!!!

え、まって、共通ルートってここからなのかよ。

ここまでは体験版ってこと?

はぇー。

導入が約6万文字か。

あら、美沙さん大胆。

清楚系変態の美沙さん。

いつもなら上から順に、この作品ならハルさんから攻略するタイプですが、今回は例外的に楓さんを先に。

ふむふむ。おっぱい。

おっぱい?

なに書こうとしたんだっけ。

あー。おっぱい、輪郭線入れるべき、というメモをしたかったの。

ハルさんの私服、材質なに?

楓さんの会話のテンポまじ好きだわ。

昔から思ってたんだけどカロリーより脂質抑えた方がいいんじゃないの?

黒髪ロングの破壊力すごい。

でもちょっと水色のリムライト違和感あるよな。

絵的には普通なんだろうけどライティングという観点から見ると違和感半端ない。

スッポンじゃなくてスッポンポンかぁ!!

語呂良くて大変草。

なんだ、草野さん、いい子じゃん。

これなら主人公さんはパシられても文句ないですわ。

学祭少し進んで眠った

シークレットラブ(仮)のネタバレを含みます。

苺クレープおいしそう!

奏命様の影響も相まって最近苺欲が沸々と沸いております。

え、あぁ。食べ過ぎ防止で禁止なのね。なら妥当だわ。

ケーキの型は持ってないよねぇ。シフォンの焼き型ならあるんだけど。

普通のケーキは苺が高いからという理由で作る気が中々起きなかった学生時代は。

レジン。単語が懐かしいわ。

aaaaaaaaaaaaaaaaaaaaaaa.

かわいいいいいいいいいいいいいい。

そうだぞ先輩、ムード壊すな、ばーか。

ふふふふふ。

SDFテクスチャ作成ツールの続き

ふふ~ん♪

制御点の配置と塗り潰し対応が済んだらようやく使える状態になりそう。

これでアニメ調な顔陰を作れるぞ。

いぇーい。

次は衣類の濡れ表現をやりたいな。

顔陰もえっちですけど、衣類の濡れはもっとえっち。

SDFテクスチャ作成ツールの続きの続き

スプラインで制御点の分布をしてみた。

32の制御点を元にSDFしてるんだけどまだ荒いんだよねぇ。

SDFテクスチャ作成ツールの続きの続きの続き

昔作成した顔陰マテリアルの大改修。

当初の予定ではスプラインで全部書く予定でしたが、鼻の陰とか頬の部分をマスク的に運用して、全体の陰は普通に関数で書いた方が綺麗かもですね。

ベイク方法はシンプルに頂点変換させてシーンキャプチャで撮影です。

かわいい。

かわいいんですけど、シェーディングが結構悩ましいんですよね。

アニメ作品に従うと画像のようなベタ塗りが正解なんですが、エロゲのようなCG絵に従うと結構ディフューズしてるんですよ。

色塗りの勉強がてらにWhirlpoolの作品も遊びたかったりします。

アンレス・テルミナリア
アンレス・テルミナリア 製品サイト。WhirlpoolオフィシャルWEBサイト。

パッケージを見て即購入したんですが、実は未プレイ。

サンプルのCG絵を見ただけなんですが、リムの表現がやたら綺麗なんですよね。

そりゃ人力で塗ってるから当然っちゃ当然なんですけど。

資料として興味は大変そそられるんですが、このブランドは驚異の未プレイ率100%

竜姫とpieces1作目は冒頭だけ少し覗いてそのまま積みました。

たぶん体験版の範囲にすら到達してない。

セレオブの逆光時に輪郭線を消す表現もいいなぁと思ったまま実装にまでは移せてない現状。

温泉のシーンだっけ?
くくるさんと蓼科のファンクラブ名称に草生えるシーン。

ちっぱいで思い出したけどリドジョも遊びたいのよね。
あの人は虚無だけど。

古のPCデータ移行失敗でセーブデータ死んだから、あの長い長い茉優先輩との日々が消えたままなのよね。

ダメだ書き始めると遊びたい欲があることを自覚するから精神衛生に悪い。

絶望的な最終プレイ日時

他にもクロスとか色々と夢中になっていたらエロゲが遠ざかりました。

コード書くのもエロゲするのも両方楽しいからバランスが取れないんですよね。

とはいえ再来週の25日は別です。

ここ最近の唯一の楽しみです。