【UE5】GBufferのCustomDataにアウトラインカラーとマスクを格納してみた – Unreal Engine 5.3

Unreal Engine

水ノ茉の宣伝

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

始まり

GBufferのCustomDataに任意の値を格納するエンジン改造をしていきます。

CustomDataとは?

読んで字の如くカスタムなデータです。

Unreal Engineには幾つかのシェーディングモデルがあり、それぞれで使用する値の種類が違います。

例えば以下のような感じ。

シェーディングモデル使用する値の種類
SubsurfaceSubsurface Color
Opacity
Clear CoatClear Coat
Clear Coat Roughness
Clear Coat Bottom Normal
ClothSubsurface Color
Cloth

これらシェーディングモデルごとに異なる種類のデータの格納先がCustomDataです。

エンコードの流れ

BasePassでGBufferに書き込むときの流れ。

// 格納したいデータたち
float3 OutlineColor = ...;
float OutlineMask = ...;
GBuffer.OutlineColor = OutlineColor;
GBuffer.OutlineMask = OutlineMask;

// 格納したい子を一時的にCustomDataに突っ込む
// シェーディングモデルごとに突っ込む内容が変わるよ
if (シェーディングモデルがToonLitの時)
{
    GBuffer.CustomData.rgb = GBuffer.OutlineColor;
    GBuffer.CustomData.a = GBuffer.OutlineMask;
}

// GBufferにCustomDataを書き込む
MRT[XX].r = GBuffer.CustomData.r;
MRT[XX].g = GBuffer.CustomData.g;
MRT[XX].b = GBuffer.CustomData.b;
MRT[XX].a = GBuffer.CustomData.a;

デコードの流れ

書き込んだGBufferを使う時の流れ。

// GBufferからCustomDataに戻す
GBuffer.CustomData.r = MRT[xx].r;
GBuffer.CustomData.g = MRT[xx].g;
GBuffer.CustomData.b = MRT[xx].b;
GBuffer.CustomData.a = MRT[xx].a;

// CustomDataから元の場所に戻す
if (シェーディングモデルがToonLitの時)
{
    GBuffer.OutlineColor = GBuffer.CustomData.rgb;
    GBuffer.OutlineMask = GBuffer.CustomData.a;
}

// 値の受け渡しの役目が終わったらゼロ埋め
GBuffer.CustomData = 0;

エンジン改造とは?

ゲームエンジンのコードを書き換える行為をエンジン改造と呼びます。

尚エンジン改造という呼称が正式かは知らんです。

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

以下、実際の描画まわりの改造事例です。

前髪を透かしたり

毛先まで綺麗に表現できるポストプロセスなアウトラインを描いたり

キャラと背景を別々のシャドウマップに書き込んでセルフシャドウを抑制したり

髪影を落としたり

頂点IDノードを追加して、それを元に鼻に線を引いたり

Substrate (旧: Strata) や Ray Tracing, Path Tracing などは非対応

この子たちは現実的なライティングをする際にお世話になります。

トゥーン・セル・アニメ調を前提とした改造方針の場合は関わることが少ないので非対応とします。

データの流し方が違うので、両対応って結構面倒なんです。

リポジトリ

最新バージョンのみ保守しています。
過去バージョンが動かないと言われても基本的には知らんがなスタイルです。

実装

GBufferのエンコード、デコードは、実装があっちこっちに散らばっていて、時系列順に説明するのが難しいです。
ざーっと目を通してから読み直す方が理解がすんなり進むと思います。

Engine/Source/Runtime/RenderCore/Public/GBufferInfo.h

アウトラインカラーとアウトラインマスクの定義を追加します。

GBufferSlotの頭文字を取ってGBSです。

githubで実装を見る(L44-L45)

Engine/Source/Runtime/Engine/Private/ShaderCompiler/ShaderGenerationUtil.cpp

先ほどのGBSの名称を取得する関数です。

名称は後述するシェーダー側のFGBufferData構造体で定義しているものと一致させる必要があります。
※尚5.3時点ではこの仕組みは整備されていないため、GBSまわりはぶっちゃけ実装スキップしてもいいです。

githubで実装を見る(L459-L462)

GBufferに格納する要素を指定しています。

CustomDataスロットを格納するパターンと、使用するスロットごとに格納する2パターンがあります。

―――まぁ、嘘なんですけどね。

SetSlotsForShadingModelType関数を使用している箇所がないため、この仕様は存在していません。

将来的にやりたいことの布石なんだろなぁ程度の認識でいいと思います。

たぶん、こんな感じね。

// 現行

// SetGBufferForShadingModel関数でCustomDataにマージ
GBuffer.CustomData.rgb = GBuffer.OutlineColor;
GBuffer.CustomData.a = GBuffer.OutlineMask;

// マージされたものを書き込み
MRT[XX] = GBuffer.CustomData.x;
MRT[XX] = GBuffer.CustomData.y;
MRT[XX] = GBuffer.CustomData.z;
MRT[XX] = GBuffer.CustomData.w;
// 予想

// SetGBufferForShadingModel関数でCustomDataにマージ
if (bMergeCustom)
{
    GBuffer.CustomData.rgb = GBuffer.OutlineColor;
    GBuffer.CustomData.a = GBuffer.OutlineMask;
}

// 実際は動的分岐じゃなくてバリエーションだろうけど、可視化の都合、動的分岐で書いてるよ
if (bMergeCustom)
{
    // マージされたものを書き込むバージョン
    MRT[XX].r = GBuffer.CustomData.x;
    MRT[XX].g = GBuffer.CustomData.y;
    MRT[XX].b = GBuffer.CustomData.z;
    MRT[XX].a = GBuffer.CustomData.w;
}
else
{
    // 使用するスロットを格納、スロットの割り当てはC++側で自動的に詰める
    MRT[XX].r = GBuffer.OutlineColor.r;
    MRT[XX].g = GBuffer.OutlineColor.g;
    MRT[XX].b = GBuffer.OutlineColor.b;
    MRT[XX].a = GBuffer.OutlineMask;
}

githubで実装を見る(L1691-L1699)

という訳で現行ではGBS_CustomDataにマージされた値を格納する仕様のみが採用される感じです。

githubで実装を見る(L1824)

Engine/Source/Runtime/RenderCore/Private/ShaderMaterialDerivedHelpers.cpp

シェーディングモデルのToonLitがCustomDataに書き込めるようにしています。

UE5からGBufferエンコード、デコードまわりのシェーダーコードがC++で作成されるようになりました。

完全な移行は出来ておらず、現状ではC++とシェーダーの両対応が必要という、保守管理がちょっと怠い感じです。

githubで実装を見る(L44-L55)

Engine/Shaders/Private/BasePassCommon.ush

「シェーディングモデルのToonLitがCustomDataに書き込めるようにしています。」のシェーダー側の対応です。

githubで実装を見る(L42-L48)

Engine/Shaders/Private/DeferredShadingCommon.ush

FGBufferData構造体にアウトラインカラーとアウトラインマスクを追加しています。

githubで実装を見る(L392-L395)

CustomDataを持っている、書き込んでいるシェーディングモデルにToonLitを追加しています。

この対応を忘れるとGBufferには書き込めているけど、デコードする際に仲間外れにされて、ゼロ埋めされるという初見殺しポイントです。

githubで実装を見る(L314-L316)

アウトラインカラーとマスクは一時的にCustomDataに入っているので、それを元の場所に移動して、CustomDataはゼロにするという部分です。

先ほど対応したHasCustomGBufferData関数の結果が偽だとCustomDataが0になる箇所も一緒に見れますね。

githubで実装を見る(L987-L997)

Engine/Shaders/Private/ShadingModelsMaterial.ush

アウトラインカラーとマスクをCustomDataに格納している箇所です。

これを忘れるとGBufferに書き込みされないです。

githubで実装を見る(L205-L211)

Engine/Shaders/Private/BasePassPixelShader.usf

SetGBufferForShadingModel関数でCustomDataに突っ込む前にカラーとマスクを格納しています。

githubで実装を見る(L994-L1000)

Engine/Shaders/Private/GBufferHelpers.ush

先ほど対応したDecodeGBufferData関数と同様です。

githubで実装を見る(L380-L390)

簡単な動作確認

GBufferに格納する部分だけ整備していて、使用する部分は未整備です。

動作確認はRenderDocで軽く見ちゃいます。

BasePassを見るとこんな感じで書き込まれている
アウトラインカラーとマスク

おわり!!!

ピンの追加と違って結構簡単なんよね。

あとはデバッグ用に可視化とポストプロセスマテリアルで使えるようにノードの対応ですね。

雑談

世界をゆっくり歩くために火力増し増しで片付け中です。

10時間空けるためにめっちゃ前倒しして頑張ってる。

FANZA通販とGAMESで特典違うの若干癪に障るわ。
後日談だけどデータ特典付いてたわ。

あと予約引換券、現実に出向かないと行けないのが面倒過ぎて毎回無駄になる。

得点目当てで何本か買う派だから割ともったいない。

もったいないからが行く理由にはならんのだけどね。

面倒が勝る。

通販なんだから一緒に忍ばせてくれたら嬉しいのになぁという軽い不満。