SPARKCREATIVE Tech Blog

https://www.spark-creative.jp/

【UE5】セルシェーディングのためにGバッファを追加してみた

こんにちは!!!クライアントエンジニアの小林です。

今回はトゥーン・セル表現を題材にエンジン改造の続きとして、Gバッファの追加をします。

Gバッファの拡張理由としては、ライティング計算の情報が増えたり、ポストエフェクトで複雑な処理をしたりなどがあると思います。

少し脱線しますが、さりげなくシェーディングモデルを追加の頃からポストエフェクトによるアウトラインを引いています。
しかしこのアウトラインには欠点があります。
それは髪の毛に線を引こうとすると目の輪郭線にまで線が引かれてしまうことです。

目の輪郭にまで線が引かれてしまう

このようなデザインが好きな人や作品の方向性的に正しいのならいいと思いますが、今回に限っては好まないです。

こんな時は頂点カラーやマスクテクスチャで、アウトラインを引く引かない情報を持たせ、それをGバッファに格納、ポストエフェクトでその情報を元に線を引くことで解決します。

前置きが長々となりましたが、今回はアウトラインを引かない情報を格納するためにGバッファを拡張していきます。

作業環境

・windows10
visual studio 2022
visual studio code
・UnrealEngine 5.0.3

改造内容

Gバッファを1枚追加して、アウトラインを引く引かない情報、以後アウトラインマスクを格納します。
ついでに前回追加したToonShadeThreshとToonShadowThreshも格納します。

改造ルール(前回、前々回同様)

UnrealEngineのエンジンコードに手を加える場合は、コメントを付けることが推奨されています。

Engine\Source

Runtime\Engine\Private\ShaderCompiler\ShaderGenerationUtil.cpp

UE5.0からGバッファに関連するコードがシェーダ直書きから、ソースコードによるシェーダジェネレータに変更されています。

これはその一部で、TEXTの中身はDeferredShadingCommon.ush内のFGBufferData構造体の変数名と同様にする必要があります。

    case GBS_IrisNormal:
        return TEXT("IrisNormal");
//// Add GBuffer:GBufferG 2022/09/19 ////
    case GBS_ToonShadeThresh:
        return TEXT("ToonShadeThresh");
    case GBS_ToonShadowThresh:
        return TEXT("ToonShadowThresh");
    case GBS_OutlineMask:
        return TEXT("OutlineMask");
//// Add GBuffer:GBufferG 2022/09/19 ////

どの情報をGバッファに書き込むかを設定しています。

このbUseCustomDataはStrata使用時にfalseになるため、PBR非対応なToonLitと相性がよさそうなため使っています。

    // doesn't write to GBuffer
    if (Mat.MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT)
    {
    }

//// Add ShadingModel:ToonLit 2022/09/17 ////
    if (Mat.MATERIAL_SHADINGMODEL_TOON_LIT)
    {
        SetStandardGBufferSlots(Slots, bWriteEmissive, bHasTangent, bHasVelocity, bHasStaticLighting, bIsStrataMaterial);
    //// Add MaterialInput:ToonShadowParam 2022/09/18 ////
        Slots[GBS_CustomData] = bUseCustomData;
    //// Add MaterialInput:ToonShadowParam 2022/09/18 ////
    //// Add GBuffer:GBufferG 2022/09/19 ////
        Slots[GBS_ToonShadeThresh] = bUseCustomData;
        Slots[GBS_ToonShadowThresh] = bUseCustomData;
        Slots[GBS_OutlineMask] = bUseCustomData;
    //// Add GBuffer:GBufferG 2022/09/19 ////
    }
//// Add ShadingModel:ToonLit 2022/09/17 ////

Runtime\Engine\Public\MaterialSceneTextureId.h

SceneTextureノードで取得できるものにアウトラインマスクを追加します。

このPPIを追加する際は要素数が32を超えないように注意してください。
ビット演算な関係で32を超えると挙動がおかしくなります。
といっても使用箇所は少ないので、該当箇所を書き換えれば32以上の場合も対応できます。

    /** Material anisotropy, single channel (GBuffer) */
    PPI_Anisotropy UMETA(DisplayName = "Anisotropy"),
//// Add GBuffer:GBufferG 2022/09/19 ////
    /** Material outline coef, single channel (GBuffer) */
    PPI_OutlineMask UMETA(DisplayName = "OutlineMask"),
//// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\Engine\Public\MaterialShared.h

            IsSceneTextureUsed(PPI_StoredSpecular) ||
            IsSceneTextureUsed(PPI_Velocity)
        //// Add GBuffer:GBufferG 2022/09/19 ////
            || IsSceneTextureUsed(PPI_OutlineMask);
        //// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\RenderCore\Private\GBufferInfo.cpp

これもシェーダジェネレータ部分です。
Gバッファの枚数は設定により増減するためそれの設定をしています。

    int32 TargetGBufferE = -1;
    int32 TargetGBufferF = -1;
//// Add GBuffer:GBufferG 2022/09/19 ////
    int32 TargetGBufferG = -1;
//// Add GBuffer:GBufferG 2022/09/19 ////
        Info.NumTargets = Params.bHasPrecShadowFactor ? 7 : 6;
    }

//// Add GBuffer:GBufferG 2022/09/19 ////
    Info.NumTargets += 1;
//// Add GBuffer:GBufferG 2022/09/19 ////
    if (Params.bHasVelocity == 0 && Params.bHasTangent == 0)
    {
        TargetGBufferD = 4;
        Info.Targets[4].Init(GBT_Unorm_8_8_8_8,  TEXT("GBufferD"), false,  true,  true,  true);

        if (Params.bHasPrecShadowFactor)
        {
            TargetGBufferE = 5;
            Info.Targets[5].Init(GBT_Unorm_8_8_8_8,  TEXT("GBufferE"), false,  true,  true,  true);
        }

    //// Add GBuffer:GBufferG 2022/09/19 ////
        TargetGBufferG = TargetGBufferE == -1 ? 5 : TargetGBufferE + 1;
    //// Add GBuffer:GBufferG 2022/09/19 ////
    }
    else if (Params.bHasVelocity)
    {
        TargetVelocity = 4;
        TargetGBufferD = 5;

        // note the false for use extra flags for velocity, not quite sure of all the ramifications, but this keeps it consistent with previous usage
        Info.Targets[4].Init(Params.bUsesVelocityDepth ? GBT_Float_16_16_16_16 : GBT_Float_16_16,   TEXT("Velocity"), false,  true,  true, false);
        Info.Targets[5].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferD"), false,  true,  true,  true);

        if (Params.bHasPrecShadowFactor)
        {
            TargetGBufferE = 6;
            Info.Targets[6].Init(GBT_Unorm_8_8_8_8,  TEXT("GBufferE"), false,  true,  true, false);
        }

    //// Add GBuffer:GBufferG 2022/09/19 ////
        TargetGBufferG = TargetGBufferE == -1 ? 6 : TargetGBufferE + 1;
    //// Add GBuffer:GBufferG 2022/09/19 ////
    }
    else if (Params.bHasTangent)
    {
        TargetGBufferF = 4;
        TargetGBufferD = 5;
        Info.Targets[4].Init(GBT_Unorm_8_8_8_8,  TEXT("GBufferF"), false,  true,  true,  true);
        Info.Targets[5].Init(GBT_Unorm_8_8_8_8,  TEXT("GBufferD"), false,  true,  true,  true);
        if (Params.bHasPrecShadowFactor)
        {
            TargetGBufferE = 6;
            Info.Targets[6].Init(GBT_Unorm_8_8_8_8,  TEXT("GBufferE"), false,  true,  true,  true);
        }

    //// Add GBuffer:GBufferG 2022/09/19 ////
        TargetGBufferG = TargetGBufferE == -1 ? 6 : TargetGBufferE + 1;
    //// Add GBuffer:GBufferG 2022/09/19 ////
    }
    else
    {
        // should never hit this path
        check(0);
    }

//// Add GBuffer:GBufferG 2022/09/19 ////
    Info.Targets[TargetGBufferG].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferG"), false, true, true, true);
//// Add GBuffer:GBufferG 2022/09/19 ////
	Info.Slots[GBS_CustomData].Packing[3] = FGBufferPacking(TargetGBufferD, 3, 3);

//// Add GBuffer:GBufferG 2022/09/19 ////
	// GBufferG
	Info.Slots[GBS_ToonShadeThresh] = FGBufferItem(GBS_ToonShadeThresh, GBC_Raw_Unorm_8, GBCH_Both);
	Info.Slots[GBS_ToonShadeThresh].Packing[0] = FGBufferPacking(TargetGBufferG, 0, 0);

	Info.Slots[GBS_ToonShadowThresh] = FGBufferItem(GBS_ToonShadowThresh, GBC_Raw_Unorm_8, GBCH_Both);
	Info.Slots[GBS_ToonShadowThresh].Packing[0] = FGBufferPacking(TargetGBufferG, 0, 1);

	Info.Slots[GBS_OutlineMask] = FGBufferItem(GBS_OutlineMask, GBC_Raw_Unorm_8, GBCH_Both);
	Info.Slots[GBS_OutlineMask].Packing[0] = FGBufferPacking(TargetGBufferG, 0, 2);
//// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\RenderCore\Public\GBufferInfo.h

    GBS_IrisNormal, // RG8
//// Add GBuffer:GBufferG 2022/09/19 ////
    GBS_ToonShadeThresh, // R8
    GBS_ToonShadowThresh, // R8
    GBS_OutlineMask, // R8
//// Add GBuffer:GBufferG 2022/09/19 ////
struct FGBufferInfo
{
//// Add GBuffer:GBufferG 2022/09/19 ////
    static const int MaxTargets = 8 + 1;
//// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\Renderer\Private\SceneRendering.cpp

FASTVRAM_CVAR(GBufferE, 0);
FASTVRAM_CVAR(GBufferF, 0);
//// Add GBuffer:GBufferG 2022/09/19 ////
FASTVRAM_CVAR(GBufferG, 0);
//// Add GBuffer:GBufferG 2022/09/19 ////
    bDirty |= UpdateTextureFlagFromCVar(CVarFastVRam_GBufferE, GBufferE);
    bDirty |= UpdateTextureFlagFromCVar(CVarFastVRam_GBufferF, GBufferF);
//// Add GBuffer:GBufferG 2022/09/19 ////
    bDirty |= UpdateTextureFlagFromCVar(CVarFastVRam_GBufferG, GBufferG);
//// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\Renderer\Private\SceneRendering.h

    ETextureCreateFlags GBufferE;
    ETextureCreateFlags GBufferF;
//// Add GBuffer:GBufferG 2022/09/19 ////
    ETextureCreateFlags GBufferG;
//// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\Renderer\Private\SceneTextureParameters.cpp

    Parameters.GBufferETexture = GetIfProduced(SceneTextures.GBufferE);
    Parameters.GBufferFTexture = GetIfProduced(SceneTextures.GBufferF, SystemTextures.MidGrey);
//// Add GBuffer:GBufferG 2022/09/19 ////
    Parameters.GBufferGTexture = GetIfProduced(SceneTextures.GBufferG);
//// Add GBuffer:GBufferG 2022/09/19 ////
    Parameters.GBufferETexture = (*SceneTextureUniformBuffer)->GBufferETexture;
    Parameters.GBufferFTexture = (*SceneTextureUniformBuffer)->GBufferFTexture;
//// Add GBuffer:GBufferG 2022/09/19 ////
    Parameters.GBufferGTexture = (*SceneTextureUniformBuffer)->GBufferGTexture;
//// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\Renderer\Private\SceneTextureParameters.h

    SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferETexture)
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferFTexture)
//// Add GBuffer:GBufferG 2022/09/19 ////
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferGTexture)
//// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\Renderer\Private\SceneTextures.cpp

            Config.GBufferD = FindGBufferBindingByName(GBufferInfo, TEXT("GBufferD"));
            Config.GBufferE = FindGBufferBindingByName(GBufferInfo, TEXT("GBufferE"));
        //// Add GBuffer:GBufferG 2022/09/19 ////
            Config.GBufferG = FindGBufferBindingByName(GBufferInfo, TEXT("GBufferG"));
        //// Add GBuffer:GBufferG 2022/09/19 ////
            Config.GBufferD = GlobalInstance.GBufferD;
            Config.GBufferE = GlobalInstance.GBufferE;
        //// Add GBuffer:GBufferG 2022/09/19 ////
            Config.GBufferG = GlobalInstance.GBufferG;
        //// Add GBuffer:GBufferG 2022/09/19 ////
            SceneTextures.GBufferF = GraphBuilder.CreateTexture(Desc, TEXT("GBufferF"));
        }

    //// Add GBuffer:GBufferG 2022/09/19 ////
        if (Config.GBufferG.Index >= 0)
        {
            const FRDGTextureDesc Desc(FRDGTextureDesc::Create2D(Config.Extent, Config.GBufferG.Format, FClearValueBinding::Transparent, Config.GBufferG.Flags | FlagsToAdd | GFastVRamConfig.GBufferG));
            SceneTextures.GBufferG = GraphBuilder.CreateTexture(Desc, TEXT("GBufferG"));
        }
    //// Add GBuffer:GBufferG 2022/09/19 ////
            { TEXT("GBufferD"), GBufferD, Config.GBufferD.Index },
            { TEXT("GBufferE"), GBufferE, Config.GBufferE.Index },
        //// Add GBuffer:GBufferG 2022/09/19 ////
            { TEXT("GBufferG"), GBufferG, Config.GBufferG.Index },
        //// Add GBuffer:GBufferG 2022/09/19 ////
    SceneTextureParameters.GBufferETexture = SystemTextures.Black;
    SceneTextureParameters.GBufferFTexture = SystemTextures.MidGrey;
//// Add GBuffer:GBufferG 2022/09/19 ////
    SceneTextureParameters.GBufferGTexture = SystemTextures.Black;
//// Add GBuffer:GBufferG 2022/09/19 ////
            if (EnumHasAnyFlags(SetupMode, ESceneTextureSetupMode::GBufferF) && HasBeenProduced(SceneTextures->GBufferF))
            {
                SceneTextureParameters.GBufferFTexture = SceneTextures->GBufferF;
            }

        //// Add GBuffer:GBufferG 2022/09/19 ////
            if (EnumHasAnyFlags(SetupMode, ESceneTextureSetupMode::GBufferG) && HasBeenProduced(SceneTextures->GBufferG))
            {
                SceneTextureParameters.GBufferGTexture = SceneTextures->GBufferG;
            }
        //// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\Renderer\Private\SceneTextures.h

    FGBufferBinding GBufferD;
    FGBufferBinding GBufferE;
//// Add GBuffer:GBufferG 2022/09/19 ////
    FGBufferBinding GBufferG;
//// Add GBuffer:GBufferG 2022/09/19 ////
	FRDGTextureRef GBufferE{};
	FRDGTextureRef GBufferF{};
//// Add GBuffer:GBufferG 2022/09/19 ////
	FRDGTextureRef GBufferG{};
//// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\Renderer\Private\PostProcess\PostProcessBufferInspector.cpp

    RDG_TEXTURE_ACCESS(GBufferE, ERHIAccess::CopySrc)
    RDG_TEXTURE_ACCESS(GBufferF, ERHIAccess::CopySrc)
//// Add GBuffer:GBufferG 2022/09/19 ////
    RDG_TEXTURE_ACCESS(GBufferG, ERHIAccess::CopySrc)
//// Add GBuffer:GBufferG 2022/09/19 ////
                        RHICmdList.CopyTexture(SourceBufferF, DestinationBufferBCDEF, CopyInfo);
                    }
                }

            //// Add GBuffer:GBufferG 2022/09/19 ////
                if (Parameters.GBufferG)
                {
                    FRHITexture* SourceBufferG = Parameters.GBufferG->GetRHI();
                    if (DestinationBufferBCDEF->GetFormat() == SourceBufferG->GetFormat())
                    {
                        FRHICopyTextureInfo CopyInfo;
                        CopyInfo.SourcePosition = SourcePoint;
                        CopyInfo.Size = FIntVector(1, 1, 1);
                        RHICmdList.CopyTexture(SourceBufferG, DestinationBufferBCDEF, CopyInfo);
                    }
                }
            //// Add GBuffer:GBufferG 2022/09/19 ////
        PassParameters->GBufferE = SceneTextures.GBufferETexture;
        PassParameters->GBufferF = SceneTextures.GBufferFTexture;
    //// Add GBuffer:GBufferG 2022/09/19 ////
        PassParameters->GBufferG = SceneTextures.GBufferGTexture;
    //// Add GBuffer:GBufferG 2022/09/19 ////

Runtime\Renderer\Public\SceneRenderTargetParameters.h

    SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferETexture)
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferFTexture)
//// Add GBuffer:GBufferG 2022/09/19 ////
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferGTexture)
//// Add GBuffer:GBufferG 2022/09/19 ////
    SSAO            = 1 << 9,
    CustomDepth     = 1 << 10,
//// Add GBuffer:GBufferG 2022/09/19 ////
    GBufferG        = 1 << 11,
    GBuffers        = GBufferA | GBufferB | GBufferC | GBufferD | GBufferE | GBufferF | GBufferG,
//// Add GBuffer:GBufferG 2022/09/19 ////

Engine\Shaders

Private\BasePassPixelShader.usf

    // PreShadowFactor
    float4 OutGBufferE = 0;
    
//// Add GBuffer:GBufferG 2022/09/19 ////
    float4 OutGBufferG = 0;
//// Add GBuffer:GBufferG 2022/09/19 ////

Private\Common.ush

struct FPixelShaderOut
{
//// Add GBuffer:GBufferG 2022/09/19 ////
    // [0..7+1], only usable if PIXELSHADEROUTPUT_MRT0, PIXELSHADEROUTPUT_MRT1, ... is 1
    float4 MRT[8+1];
//// Add GBuffer:GBufferG 2022/09/19 ////

Private\DecalCommon.ush

シェーダジェネレータに変更された割にデカールなどは旧仕様なシェーダ直書きです。
対応するなら全てシェーダジェネレータに変更してほしかったです。個別対応が地味に面倒。

今回追加するGバッファはBasePassとPostProcessPassでのみの使用を想定しているため、それ以外の場所はダミーな値を仕込んでおきます。

    float4 OutTarget6 = 0;
//// Add GBuffer:GBufferG 2022/09/19 ////
    float4 OutTarget7 = 0;
//// Add GBuffer:GBufferG 2022/09/19 ////

    #if DECAL_RENDERTARGETMODE == DECAL_RENDERTARGETMODE_GBUFFER
    {
    //// Add GBuffer:GBufferG 2022/09/19 ////
        EncodeGBuffer(Data, Out.MRT[1], Out.MRT[2], Out.MRT[3], OutTarget4, OutTarget5, OutTarget6, OutTarget7);
    //// Add GBuffer:GBufferG 2022/09/19 ////
    }
    #elif DECAL_RENDERTARGETMODE == DECAL_RENDERTARGETMODE_GBUFFER_NONORMAL
    {
    //// Add GBuffer:GBufferG 2022/09/19 ////
        EncodeGBuffer(Data, OutTarget1, Out.MRT[1], Out.MRT[2], OutTarget4, OutTarget5, OutTarget6, OutTarget7);
    //// Add GBuffer:GBufferG 2022/09/19 ////

Private\DeferredShadingCommon.ush

Texture2D GBufferDTexture;
Texture2D GBufferETexture;
//// Add GBuffer:GBufferG 2022/09/19 ////
Texture2D GBufferGTexture;
//// Add GBuffer:GBufferG 2022/09/19 ////
#define GBufferETextureSampler GlobalPointClampedSampler
#define GBufferFTextureSampler GlobalPointClampedSampler
//// Add GBuffer:GBufferG 2022/09/19 ////
#define GBufferGTextureSampler GlobalPointClampedSampler
//// Add GBuffer:GBufferG 2022/09/19 ////
    // 0..1, only needed by SHADINGMODELID_EYE which encodes Iris Distance inside Metallic
    float StoredMetallic;

//// Add GBuffer:GBufferG 2022/09/19 ////
    // -1..1
    float ToonShadeThresh;
    // 0..1
    float ToonShadowThresh;
    // 0..1
    float OutlineMask;
//// Add GBuffer:GBufferG 2022/09/19 ////
    out float4 OutGBufferD,
    out float4 OutGBufferE,
//// Add GBuffer:GBufferG 2022/09/19 ////
    out float4 OutGBufferG,
//// Add GBuffer:GBufferG 2022/09/19 ////
        OutGBufferD = 0;
        OutGBufferE = 0;
    //// Add GBuffer:GBufferG 2022/09/19 ////
        OutGBufferG = 0;
    //// Add GBuffer:GBufferG 2022/09/19 ////
        OutGBufferD = GBuffer.CustomData;
        OutGBufferE = GBuffer.PrecomputedShadowFactors;
    //// Add GBuffer:GBufferG 2022/09/19 ////
        OutGBufferG.r = GBuffer.ToonShadeThresh;
        OutGBufferG.g = GBuffer.ToonShadowThresh;
        OutGBufferG.b = GBuffer.OutlineMask;
        OutGBufferG.a = 0;
    //// Add GBuffer:GBufferG 2022/09/19 ////
    float4 InGBufferE,
    float4 InGBufferF,
//// Add GBuffer:GBufferG 2022/09/19 ////
    float4 InGBufferG,
//// Add GBuffer:GBufferG 2022/09/19 ////
    GBuffer.Velocity = !(GBuffer.SelectiveOutputMask & SKIP_VELOCITY_MASK) ? InGBufferVelocity : 0;

//// Add GBuffer:GBufferG 2022/09/19 ////
    GBuffer.ToonShadeThresh = InGBufferG.x * 2.0f - 1.0f;
    GBuffer.ToonShadowThresh = InGBufferG.y;
    GBuffer.OutlineMask = InGBufferG.z;
//// Add GBuffer:GBufferG 2022/09/19 ////
            float4 GBufferVelocity = 0;
        #endif

    //// Add GBuffer:GBufferG 2022/09/19 ////
        float4 GBufferG = SceneTexturesStruct.GBufferGTexture.Load(int3(PixelPos, 0));
    //// Add GBuffer:GBufferG 2022/09/19 ////

        float SceneDepth = CalcSceneDepth(PixelPos);

    //// Add GBuffer:GBufferG 2022/09/19 ////
        return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferG, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromPixelPos(PixelPos));
    //// Add GBuffer:GBufferG 2022/09/19 ////
    float4 GBufferE = GBufferETexture.SampleLevel(GBufferETextureSampler, UV, 0);
    float4 GBufferF = GBufferFTexture.SampleLevel(GBufferFTextureSampler, UV, 0);
//// Add GBuffer:GBufferG 2022/09/19 ////
    float4 GBufferG = GBufferGTexture.SampleLevel(GBufferGTextureSampler, UV, 0);
//// Add GBuffer:GBufferG 2022/09/19 ////
    float SceneDepth = ConvertFromDeviceZ(DeviceZ);

//// Add GBuffer:GBufferG 2022/09/19 ////
    return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferG, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV));
//// Add GBuffer:GBufferG 2022/09/19 ////
        float4 GBufferVelocity = 0;
    #endif

//// Add GBuffer:GBufferG 2022/09/19 ////
    float4 GBufferG = Texture2DSampleLevel(SceneTexturesStruct.GBufferGTexture, SceneTexturesStruct_GBufferGTextureSampler, UV, 0);
//// Add GBuffer:GBufferG 2022/09/19 ////

    float SceneDepth = CalcSceneDepth(UV);
    
//// Add GBuffer:GBufferG 2022/09/19 ////
    return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferG, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(UV));
//// Add GBuffer:GBufferG 2022/09/19 ////

Private\GBufferHelpers.ush

Gバッファデコード時に値を変えたい場合に記述する箇所です。

    // This requires cleanup. Shader code that uses GBuffer.SelectiveOutputMask expects the outputmask to be in
    // bits [4:7], but it gets packed as bits [0:3] in the flexible gbuffer since we might move it around.
    Ret.SelectiveOutputMask = Ret.SelectiveOutputMask << 4;

//// Add GBuffer:GBufferG 2022/09/19 ////
    Ret.ToonShadeThresh = Ret.ToonShadeThresh * 2.0f - 1.0f;
//// Add GBuffer:GBufferG 2022/09/19 ////

Private\MaterialTemplate.ush

#define PPI_WorldTangent 29
#define PPI_Anisotropy 30
//// Add GBuffer:GBufferG 2022/09/19 ////
#define PPI_OutlineMask 31
//// Add GBuffer:GBufferG 2022/09/19 ////
        case PPI_Anisotropy:
            return ScreenSpaceData.GBuffer.Anisotropy;
    //// Add GBuffer:GBufferG 2022/09/19 ////
        case PPI_OutlineMask:
            return ScreenSpaceData.GBuffer.OutlineMask;
    //// Add GBuffer:GBufferG 2022/09/19 ////
half GetMaterialToonShadowThresh(in out FMaterialPixelParameters Parameters)
{
    return GetMaterialToonShadowParam(Parameters).y;
}
//// Add MaterialInput:ToonShadowParam 2022/09/18 ////
//// Add GBuffer:GBufferG 2022/09/19 ////
half GetMaterialOutlineMask(in out FMaterialPixelParameters Parameters)
{
    return GetMaterialToonShadowParam(Parameters).z;
}
//// Add GBuffer:GBufferG 2022/09/19 ////

Private\PixelShaderOutputCommon.ush

最大MRT数に合わせた対応をします。

#ifndef PIXELSHADEROUTPUT_MRT7
    #define PIXELSHADEROUTPUT_MRT7 0
#endif
//// Add GBuffer:GBufferG 2022/09/19 ////
#ifndef PIXELSHADEROUTPUT_MRT8
    #define PIXELSHADEROUTPUT_MRT8 0
#endif
//// Add GBuffer:GBufferG 2022/09/19 ////
#if PIXELSHADEROUTPUT_MRT7
        , out float4 OutTarget7 : SV_Target7
#endif

//// Add GBuffer:GBufferG 2022/09/19 ////
#if PIXELSHADEROUTPUT_MRT8
        , out float4 OutTarget8 : SV_Target8
#endif
//// Add GBuffer:GBufferG 2022/09/19 ////
#if PIXELSHADEROUTPUT_MRT7
    OutTarget7 = PixelShaderOut.MRT[7];
#endif

//// Add GBuffer:GBufferG 2022/09/19 ////
#if PIXELSHADEROUTPUT_MRT8
    OutTarget8 = PixelShaderOut.MRT[8];
#endif
//// Add GBuffer:GBufferG 2022/09/19 ////

Private\SceneTexturesCommon.ush

#define SceneTexturesStruct_GBufferETextureSampler SceneTexturesStruct.PointClampSampler
#define SceneTexturesStruct_GBufferFTextureSampler SceneTexturesStruct.PointClampSampler
//// Add GBuffer:GBufferG 2022/09/19 ////
#define SceneTexturesStruct_GBufferGTextureSampler SceneTexturesStruct.PointClampSampler
//// Add GBuffer:GBufferG 2022/09/19 ////

Private\ShadingModelsMaterial.ush

//// Add MaterialInput:ToonShadowParam 2022/09/18 ////
#if MATERIAL_SHADINGMODEL_TOON_LIT
    else if (ShadingModel == SHADINGMODELID_TOON_LIT)
    {
    //// Add GBuffer:GBufferG 2022/09/19 ////
        GBuffer.CustomData = 0.0;
        GBuffer.ToonShadeThresh = saturate(GetMaterialToonShadeThresh(MaterialParameters));
        GBuffer.ToonShadowThresh = saturate(GetMaterialToonShadowThresh(MaterialParameters));
        GBuffer.OutlineMask = saturate(GetMaterialOutlineMask(MaterialParameters));
    //// Add GBuffer:GBufferG 2022/09/19 ////
    }
#endif
//// Add MaterialInput:ToonShadowParam 2022/09/18 ////

Private\Lumen\FinalGather\LumenProbeOcclusionPass.ush

    float4 GBufferF = 0.5f;
#endif
//// Add GBuffer:GBufferG 2022/09/19 ////
    float4 GBufferG = 0.0f; // Dummy
//// Add GBuffer:GBufferG 2022/09/19 ////
     bool bGetNormalizedNormal = true;
//// Add GBuffer:GBufferG 2022/09/19 ////
    FGBufferData GBuffer = DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferG, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(BufferUV));
//// Add GBuffer:GBufferG 2022/09/19 ////

Private\RayTracing\RayTracingDeferredShadingCommon.ush

    float4 GBufferE = GBufferETexture.Load(int3(PixelCoord, 0));
    float4 GBufferF = GBufferFTexture.Load(int3(PixelCoord, 0));
//// Add GBuffer:GBufferG 2022/09/19 ////
    float4 GBufferG = 0.0f; // Dummy
//// Add GBuffer:GBufferG 2022/09/19 ////
    float DeviceZ = SceneDepthTexture.Load(int3(PixelCoord, 0)).r;;

    float SceneDepth = ConvertFromDeviceZ(DeviceZ);

//// Add GBuffer:GBufferG 2022/09/19 ////
    return DecodeGBufferData(GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferG, GBufferVelocity, CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromPixelPos(PixelCoord));
//// Add GBuffer:GBufferG 2022/09/19 ////

Private\ScreenSpaceDenoise\SSDCommon.ush

        float4 GBufferE = 0.0;
        float4 GBufferF = 0.5f;
    //// Add GBuffer:GBufferG 2022/09/19 ////
        float4 GBufferG = 0.0f; // Dummy
    //// Add GBuffer:GBufferG 2022/09/19 ////
        float4 GBufferVelocity = 0.0;

        bool bGetNormalizedNormal = false;

        GBufferData = DecodeGBufferData(
        //// Add GBuffer:GBufferG 2022/09/19 ////
            GBufferA, GBufferB, GBufferC, GBufferD, GBufferE, GBufferF, GBufferG, GBufferVelocity,
        //// Add GBuffer:GBufferG 2022/09/19 ////
            CustomNativeDepth, CustomStencil, SceneDepth, bGetNormalizedNormal, CheckerFromSceneColorUV(BufferUV));

追加したGバッファに合わせてポストエフェクトを改良してみる

使用モデルの素材にHakka_skin_mask_outlineという明らかにアウトラインマスクなテクスチャが存在したためそちらを使用していきます。
テクスチャが無い場合は頂点カラーを使うのが簡単だと思います。UnrealEngineで頂点カラーの編集もできますし。

ToonShadowParamのz値がアウトラインマスクの格納場所のため、それに合わせてノードを組みます。

ノードを組んだらテクスチャをセット。

アウトラインポストエフェクトに、アウトラインマスクノードを組み込んで。

想定通り目の輪郭に線が引かれることなく、髪の毛に線が掛かるようになりました。

おわり

お疲れさまでした!!!

執筆当時は5.0.3が最新だったんですが、いまや5.2、開発版だと5.3ですね。
ネタを温めすぎた結果、腐りました。