시작하며
GBuffer는 디퍼드 렌더링에서 여러가지 정보들을 저장하고 각 셰이더에서는 이 정보들을 가지고 연산을 수행해요. UE에선 색상, 메탈릭, 스페큘러, 노말, 탄젠트, 뎁스 등등을 64Byte로 패킹해서 쓰고 있는데 이 크기를 넘는 것은 사실 피해야하지만 일단 어떻게 수정하고 어떻게 써야하는지 알아보기 위해 지난 글에서 추가한 커스텀 머티리얼 아웃풋의 첫번째 핀인 half4 형식의 CustomOutput01로 들어오는 값을 GBuffer에 넣어 볼게요. 머티리얼 프로토타입을 제작해서 테스트를 해보고 도메인에서 불표한 핀을 재활용하는 방식으로 셰이딩 모델을 완성하면 되겠습니다.
엔진 코드 수정하기
SceneTexturesConfig.h
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FSceneTextureUniformParameters, ENGINE_API)
// [...]
// [ CUSTOM ] Expanding Gbuffer
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferGTexture)
// [...]
END_GLOBAL_SHADER_PARAMETER_STRUCT()
GBufferInfo.h
enum EGBufferSlot
{
// [...]
GBS_MatData0, // [ CUSTOM ] Expanding GBuffer, RGBA8, no compression
// [...]
}
struct FGBufferBindings
{
// [...]
// [ CUSTOM ] Expanding GBuffer
FGBufferBinding GBufferG;
// [...]
}
SceneRenderTargetParameters.h
*5.3
enum class ESceneTexture
{
Color,
Depth,
SmallDepth,
Velocity,
GBufferA,
GBufferB,
GBufferC,
GBufferD,
GBufferE,
GBufferF,
// [ CUSTOM ] Expanding GBuffer
GBufferG,
SSAO,
CustomDepth,
};
enum class ESceneTextureSetupMode : uint32
{
// [...]
// [ CUSTOM ] Expanding GBuffer
GBufferG = 1 << 9,
SSAO = 1 << 10, // 9
CustomDepth = 1 << 11, // 10
GBuffers = GBufferA | GBufferB | GBufferC | GBufferD | GBufferE | GBufferF |/*CUSTOM*/ GBufferG,
// [...]
}
SceneTextureParameters.h
BEGIN_SHADER_PARAMETER_STRUCT(FSceneTextureParameters, )
// [...]
// [ CUSTOM ] Expanding GBuffer
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, GBufferGTexture)
// [...]
END_SHADER_PARAMETER_STRUCT()
SceneTextures.h
*5.3
struct FSceneTextures : public FMinimalSceneTextures
struct RENDERER_API FSceneTextures : public FMinimalSceneTextures
{
// [...]
// [ CUSTOM ] Expanding GBuffer
FRDGTextureRef GBufferG{};
// [...]
}
SceneRendering.h
struct FFastVramConfig
{
// [...]
// [ CUSTOM ] Expanding GBuffer
ETextureCreateFlags GBufferG;
// [...]
}
HLSLMaterialTranslator.h
// [ CUSTOM ] Expanding GBuffer
#include "Materials/MaterialExpressionCustomLitMaterialOutput.h"
헤더에서 GBufferG라고 명명한 커스텀 GBuffer 항목과 GBufferG가 저장될 씬 텍스처를 선언합니다
SceneTexturesConfig.cpp
void FSceneTexturesConfig::Init(const FSceneTexturesConfigInitSettings& InitSettings)
{
// [...]
// [ CUSTOM ] Expanding GBuffer
BindingCache.Bindings[Layout].GBufferG = FindGBufferBindingByName(GBufferInfo, TEXT("GBufferG"));
// [...]
}
uint32 FSceneTexturesConfig::GetGBufferRenderTargetsInfo(FGraphicsPipelineRenderTargetsInfo& RenderTargetsInfo, EGBufferLayout Layout) const
{
// [...]
// [ CUSTOM ] Expanding GBuffer
IncludeBindingIfValid(Bindings.GBufferG);
// [...]
}
Material.cpp
// [ CUSTOM ] Expanding GBuffer
#include "Materials/MaterialExpressionCustomLitMaterialOutput.h"
MaterialExpressions.cpp
// [ CUSTOM ] Expanding GBuffer
#include "Materials/MaterialExpressionCustomLitMaterialOutput.h"
ShaderGenerationUtil.cpp
static FString GetSlotTextName(EGBufferSlot Slot)
{
// [...]
// [ CUSTOM ] Expanding GBuffer
case GBS_MatData0:
return TEXT("MatData0");
// [...]
}
static void DetermineUsedMaterialSlots(
bool Slots[],
const FShaderMaterialDerivedDefines& Dst,
const FShaderMaterialPropertyDefines& Mat,
const FShaderLightmapPropertyDefines& Lightmap,
const FShaderGlobalDefines& SrcGlobal,
const FShaderCompilerDefines& Compiler,
ERHIFeatureLevel::Type FEATURE_LEVEL)
{
// [...]
// [ CUSTOM ] Subsurface Color as custom data
if (Mat.MATERIAL_SHADINGMODEL_CUSTOM_LIT)
{
SetStandardGBufferSlots(Slots, bWriteEmissive, bHasTangent, bHasVelocity, bHasStaticLighting, bIsStrataMaterial);
Slots[GBS_CustomData] = bUseCustomData;
// [ CUSTOM ] Expanding GBuffer
Slots[GBS_MatData0] = true;
}
// [...]
}
GBufferInfo.cpp
TArray < EGBufferSlot > FetchGBufferSlots(bool bHasVelocity, bool bHasTangent, bool bHasPrecShadowFactor)
{
// [...]
// [ CUSTOM ] Expanding GBuffer
NeededSlots.Push(GBS_MatData0);
// […]
}
FGBufferInfo RENDERCORE_API FetchLegacyGBufferInfo(const FGBufferParams& Params)
{
// [...]
// [ CUSTOM ] Expanding GBuffer
int32 TargetGBufferG = -1;
// [...]
// This code should match TBasePassPS
if (Params.bHasVelocity == 0 && Params.bHasTangent == 0)
{
TargetGBufferD = 4;
// [ CUSTOM ] Expanding GBuffer
TargetGBufferG = 5;
Info.Targets[4].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferD"), false, true, true, true);
// [ CUSTOM ] Expanding GBuffer
Info.Targets[5].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferG"), false, true, true, true);
// [ CUSTOM ] Expanding GBuffer : add 1
TargetSeparatedMainDirLight = 6; // 5
if (Params.bHasPrecShadowFactor)
{
TargetGBufferE = 6; // 5
Info.Targets[6].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferE"), false, true, true, true); // 5
TargetSeparatedMainDirLight = 7; // 6
}
}
else if (Params.bHasVelocity)
{
TargetVelocity = 4;
TargetGBufferD = 5;
// [ CUSTOM ] Expanding GBuffer
TargetGBufferG = 6;
// 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_Unorm_16_16_16_16 : (IsAndroidOpenGLESPlatform(Params.ShaderPlatform) ? GBT_Float_16_16 : GBT_Unorm_16_16), TEXT("Velocity"), false, true, true, false);
Info.Targets[5].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferD"), false, true, true, true);
// [ CUSTOM ] Expanding GBuffer
Info.Targets[6].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferG"), false, true, true, true);
// [ CUSTOM ] Expanding GBuffer : add 1
TargetSeparatedMainDirLight = 7; // 6
if (Params.bHasPrecShadowFactor)
{
TargetGBufferE = 7; // 6
Info.Targets[7].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferE"), false, true, true, false); // 6
TargetSeparatedMainDirLight = 8; // 7
}
}
else if (Params.bHasTangent)
{
TargetGBufferF = 4;
TargetGBufferD = 5;
// [ CUSTOM ] Expanding GBuffer
TargetGBufferG = 6;
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);
// [ CUSTOM ] Expanding GBuffer
Info.Targets[6].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferG"), false, true, true, true);
// [ CUSTOM ] Expanding GBuffer : add 1
TargetSeparatedMainDirLight = 7; // pre 6
if (Params.bHasPrecShadowFactor)
{
TargetGBufferE = 7; // 6
Info.Targets[7].Init(GBT_Unorm_8_8_8_8, TEXT("GBufferE"), false, true, true, true); // 6
TargetSeparatedMainDirLight = 8; // 7
}
}
// [...]
// [ CUSTOM ] Expanding GBuffer
Info.Slots[GBS_MatData0] = FGBufferItem(GBS_MatData0, GBC_Raw_Unorm_8_8_8_8, GBCH_Both);
Info.Slots[GBS_MatData0].Packing[0] = FGBufferPacking(TargetGBufferG, 0, 0);
Info.Slots[GBS_MatData0].Packing[1] = FGBufferPacking(TargetGBufferG, 1, 1);
Info.Slots[GBS_MatData0].Packing[2] = FGBufferPacking(TargetGBufferG, 2, 2);
Info.Slots[GBS_MatData0].Packing[3] = FGBufferPacking(TargetGBufferG, 3, 3);
return Info;
}
SceneRendering.cpp
// [ CUSTOM ] Expanding GBuffer
FASTVRAM_CVAR(GBufferG, 0);
void FFastVramConfig::Update()
{
// [...]
// [ CUSTOM ] Expanding GBuffer
bDirty |= UpdateTextureFlagFromCVar(CVarFastVRam_GBufferG, GBufferG);
// [...]
}
SceneTextureParameters.cpp
FSceneTextureParameters GetSceneTextureParameters(FRDGBuilder& GraphBuilder, const FSceneTextures& SceneTextures)
{
// [...]
// [ CUSTOM] Expanding GBuffer
Parameters.GBufferGTexture = GetIfProduced(SceneTextures.GBufferG);
// [...]
}
FSceneTextureParameters GetSceneTextureParameters(FRDGBuilder& GraphBuilder, TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTextureUniformBuffer)
{
// [...]
// [ CUSTOM ] Expanding GBuffer
Parameters.GBufferGTexture = (*SceneTextureUniformBuffer)->GBufferGTexture;
// [...]
}
SceneTextures.cpp
void FSceneTextures::InitializeViewFamily(FRDGBuilder& GraphBuilder, FViewFamilyInfo& ViewFamily)
{
// [...]
// [ CUSTOM ] Expanding GBuffer
if (Bindings.GBufferG.Index >= 0)
{
const FRDGTextureDesc Desc(FRDGTextureDesc::Create2D(Config.Extent, Bindings.GBufferG.Format, FClearValueBinding::Transparent, Bindings.GBufferG.Flags | FlagsToAdd | GFastVRamConfig.GBufferG));
SceneTextures.GBufferG = GraphBuilder.CreateTexture(Desc, TEXT("GBufferG"));
}
// [...]
}
uint32 FSceneTextures::GetGBufferRenderTargets(
TStaticArray<FTextureRenderTargetBinding,
MaxSimultaneousRenderTargets>& RenderTargets,
EGBufferLayout Layout) const
{
// [...]
const FGBufferEntry GBufferEntries[] =
{
{ TEXT("GBufferA"), GBufferA, Bindings.GBufferA.Index },
{ TEXT("GBufferB"), GBufferB, Bindings.GBufferB.Index },
{ TEXT("GBufferC"), GBufferC, Bindings.GBufferC.Index },
{ TEXT("GBufferD"), GBufferD, Bindings.GBufferD.Index },
{ TEXT("GBufferE"), GBufferE, Bindings.GBufferE.Index },
{ TEXT("Velocity"), Velocity, Bindings.GBufferVelocity.Index },
// [ CUSTOM ] Expanding GBuffer
{ TEXT("GBufferG"), GBufferG, Bindings.GBufferG.Index }
};
// [...]
}
*5.3*
FRDGTextureRef GetSceneTexture(const FSceneTextures& SceneTextures, ESceneTexture InSceneTexture)
{
// [...]
// [ CUSTOM ] Expanding GBuffer
case ESceneTexture::GBufferG: return SceneTextures.GBufferG;
// [...]
}
void SetupSceneTextureUniformParameters(
FRDGBuilder& GraphBuilder,
const FSceneTextures* SceneTextures,
ERHIFeatureLevel::Type FeatureLevel,
ESceneTextureSetupMode SetupMode,
FSceneTextureUniformParameters& SceneTextureParameters)
{
// [...]
// [ CUSTOM ] Expanding GBuffer
SceneTextureParameters.GBufferGTexture = SystemTextures.Black;
// [...]
// [ CUSTOM ] Expanding GBuffer
if (EnumHasAnyFlags(SetupMode, ESceneTextureSetupMode::GBufferG) && HasBeenProduced(SceneTextures->GBufferG))
{
SceneTextureParameters.GBufferGTexture = SceneTextures->GBufferG;
}
// […]
}
커스텀 항목을 셰이더에 바인딩하고 렌더 타겟을 지정 합니다
셰이더 코드 수정하기
DeferredShadingCommon.ush
// [ CUSTOM ] Expanding GBuffer
Texture2D GBufferGTexture;
// [...]
// [ CUSTOM ] Expanding GBuffer
#define GBufferGTextureSampler GlobalPointClampedSampler
// [...]
struct FGBufferData
{
// [...]
// [ CUSTOM ] Expanding GBuffer
Half4 MatData0;
// [...]
}
SceneTexturesCommon.ush
// [ CUSTOM ] Expanding GBuffer
#define SceneTexturesStruct_GBufferGTextureSampler SceneTexturesStruct.PointClampSampler
ShadingModelsMaterial.ush
void SetGBufferForShadingModel(
in out FGBufferData GBuffer,
in out FMaterialPixelParameters MaterialParameters,
const float Opacity,
const half3 BaseColor,
const half Metallic,
const half Specular,
const float Roughness,
const float Anisotropy,
const float3 SubsurfaceColor,
const float SubsurfaceProfile,
const float Dither,
const uint ShadingModel,
// [ CUSTOM ] Expanding GBuffer
const float4 MatData0)
{
// [...]
// [ CUSTOM ] Subsurface Color as custom data
#if MATERIAL_SHADINGMODEL_CUSTOM_LIT
else if (ShadingModel == SHADINGMODELID_CUSTOM_LIT)
{
GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor);
GBuffer.CustomData.a = Opacity;
// [ CUSTOM ] Expanding GBuffer
GBuffer.MatData0 = MatData0;
}
#endif
// [...]
}
BasePassPixelShader.usf
void FPixelShaderInOut_MainPS(
FVertexFactoryInterpolantsVSToPS Interpolants,
FBasePassInterpolantsVSToPS BasePassInterpolants,
in FPixelShaderIn In,
inout FPixelShaderOut Out)
{
// [...]
// [ CUSTOM ] Expanding GBuffer
float4 OutGBufferG = 0;
// [...]
FGBufferData GBuffer = (FGBufferData)0;
GBuffer.GBufferAO = MaterialAO;
GBuffer.PerObjectGBufferData = GetPrimitive_PerObjectGBufferData(MaterialParameters.PrimitiveId);
GBuffer.Depth = MaterialParameters.ScreenPosition.w;
GBuffer.PrecomputedShadowFactors = GetPrecomputedShadowMasks(LightmapVTPageTableResult, Interpolants, MaterialParameters, VolumetricLightmapBrickTextureUVs);
// [ CUSTOM ] Expanding GBuffer
half4 AuxColor0 = 0;
half3 AuxColor1 = 0;
half3 AuxColor2 = 0;
#if MATERIAL_SHADINGMODEL_CUSTOM_LIT
// Getting custom material parameters
AuxColor0 = max(0.0f, LWCToFloat(GetMabiLitMaterialOutput0(MaterialParameters)));
AuxColor1 = max(0.0f, LWCToFloat(GetMabiLitMaterialOutput1(MaterialParameters)));
AuxColor2 = max(0.0f, LWCToFloat(GetMabiLitMaterialOutput2(MaterialParameters)));
#endif
#if !STRATA_ENABLED || STRATA_INLINE_SINGLELAYERWATER
// Use GBuffer.ShadingModelID after SetGBufferForShadingModel(..) because the ShadingModel input might not be the same as the output
SetGBufferForShadingModel(
GBuffer,
MaterialParameters,
Opacity,
BaseColor,
Metallic,
Specular,
Roughness,
Anisotropy,
SubsurfaceColor,
SubsurfaceProfile,
Dither,
ShadingModel,
// [ CUSTOM ] Expanding GBuffer
AuxColor0
);
#endif // !STRATA_ENABLED
// [...]
}
여기까지 해서 수정을 마치면 다른 셰이더 파일에서도 GBufferG에 접근할 수 있어요
마치며
셰이딩 모델을 추가하고 그 외에 필요한 세팅만 하는데도 이렇게 글이 길어졌네요. 에픽은 엔진 코드를 사용자가 직접 수정할 수있도록 해놓았지만 이에 대한 가이드는 별도로 마련해 놓고 있진 않아요. 엔진이 성능을 잘 낼 수 있도록 가급적 디폴트를 이용하라는 의도인 듯 싶지만 프로젝트를 진행하다가 보면 이런저런 벽에 부딪히게 되므로 소스 코드의 수정은 필수적인 것 같습니다. 셰이딩 모델을 완성 시켜가는 과정은 잠깐 쉬고 간단한 블루프린트를 제작하면서 어떻게 진행할 지 고민해 봐야겠네요.
참고
New shading models and changing the GBuffer | Community tutorial
Implementing a Celshading model directly into UE5.1 source. This celshading use a linear color curve atlas to drive all the values. Learn how to set you...
dev.epicgames.com
'UE5' 카테고리의 다른 글
[UE5]Custom Shader : PART4_Custom Material Ouput (1) | 2023.10.16 |
---|---|
[UE5]Custom Shader : PART3_Using Custom Data (1) | 2023.10.10 |
[UE5]Custom Shader : PART3_Adding shading models_2 (0) | 2023.09.27 |
[UE5]Custom Shader : PART1_Adding shading models_1 (0) | 2023.09.19 |