본문 바로가기
UE5

[UE5]Custom Shader : PART5_Editing GBuffer

by kaynine 2023. 10. 26.

시작하며

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에 접근할 수 있어요


마치며

셰이딩 모델을 추가하고 그 외에 필요한 세팅만 하는데도 이렇게 글이 길어졌네요. 에픽은 엔진 코드를 사용자가 직접 수정할 수있도록 해놓았지만 이에 대한 가이드는 별도로 마련해 놓고 있진 않아요. 엔진이 성능을 잘 낼 수 있도록 가급적 디폴트를 이용하라는 의도인 듯 싶지만 프로젝트를 진행하다가 보면 이런저런 벽에 부딪히게 되므로 소스 코드의 수정은 필수적인 것 같습니다. 셰이딩 모델을 완성 시켜가는 과정은 잠깐 쉬고 간단한 블루프린트를 제작하면서 어떻게 진행할 지 고민해 봐야겠네요.


참고

https://dev.epicgames.com/community/learning/tutorials/2R5x/unreal-engine-new-shading-models-and-changing-the-gbuffer

 

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