새 셰이더 모델 만들기

New shading models and changing the GBuffer

  • OS: Windows11
  • Engine: Unreal Engine 5.3.2

C++

EngineTypes.h 에서 enum EMaterialShadingModel 멤버중 Strata아래에 추가해 준다.

UENUM()
enum EMaterialShadingModel : int
{
	MSM_Unlit					UMETA(DisplayName="Unlit"),
	MSM_DefaultLit				UMETA(DisplayName="Default Lit"),
	MSM_Subsurface				UMETA(DisplayName="Subsurface"),
	MSM_PreintegratedSkin		UMETA(DisplayName="Preintegrated Skin"),
	MSM_ClearCoat				UMETA(DisplayName="Clear Coat"),
	MSM_SubsurfaceProfile		UMETA(DisplayName="Subsurface Profile"),
	MSM_TwoSidedFoliage			UMETA(DisplayName="Two Sided Foliage"),
	MSM_Hair					UMETA(DisplayName="Hair"),
	MSM_Cloth					UMETA(DisplayName="Cloth"),
	MSM_Eye						UMETA(DisplayName="Eye"),
	MSM_SingleLayerWater		UMETA(DisplayName="SingleLayerWater"),
	MSM_ThinTranslucent			UMETA(DisplayName="Thin Translucent"),
	MSM_Strata					UMETA(DisplayName="Substrate", Hidden),
	// TyT
	MSM_ToonLit					UMETA(DisplayName = "ToonLit"),
	// TyT
	/** Number of unique shading models. */
	MSM_NUM						UMETA(Hidden),
	/** Shading model will be determined by the Material Expression Graph,
		by utilizing the 'Shading Model' MaterialAttribute output pin. */
	MSM_FromMaterialExpression	UMETA(DisplayName="From Material Expression"),
	MSM_MAX
};

이 부분은 머티리얼 편집기에서 셰이딩 모델을 선택하는 콤보박스에 나타나게 된다.

MaterialShader.cppGetShadingModelString()함수에 새로운 셰이딩 모델을 추가해준다.
그리고 같은 파일에서 UpdateMaterialShaderCompilingStats()함수에도 코드를 추가해 준다. 이 부분은 마티리얼의 Instruction Count와 Stats을 추적하는데 도움이 되는 부분 같다.

/** Converts an EMaterialShadingModel to a string description. */
FString GetShadingModelString(EMaterialShadingModel ShadingModel)
{
	FString ShadingModelName;
	switch(ShadingModel)
	{
		case MSM_Unlit:				ShadingModelName = TEXT("MSM_Unlit"); break;
		case MSM_DefaultLit:		ShadingModelName = TEXT("MSM_DefaultLit"); break;
		case MSM_Subsurface:		ShadingModelName = TEXT("MSM_Subsurface"); break;
		case MSM_PreintegratedSkin:	ShadingModelName = TEXT("MSM_PreintegratedSkin"); break;
		case MSM_ClearCoat:			ShadingModelName = TEXT("MSM_ClearCoat"); break;
		case MSM_SubsurfaceProfile:	ShadingModelName = TEXT("MSM_SubsurfaceProfile"); break;
		case MSM_TwoSidedFoliage:	ShadingModelName = TEXT("MSM_TwoSidedFoliage"); break;
		case MSM_Hair:				ShadingModelName = TEXT("MSM_Hair"); break;
		case MSM_Cloth:				ShadingModelName = TEXT("MSM_Cloth"); break;
		case MSM_Eye:				ShadingModelName = TEXT("MSM_Eye"); break;
		case MSM_SingleLayerWater:	ShadingModelName = TEXT("MSM_SingleLayerWater"); break;
		case MSM_ThinTranslucent:	ShadingModelName = TEXT("MSM_ThinTranslucent"); break;
		case MSM_ToonLit:			ShadingModelName = TEXT("MSM_ToonLit"); break;	// TyT
		default: ShadingModelName = TEXT("Unknown"); break;
	}
	return ShadingModelName;
}
// ...
/** Called for every material shader to update the appropriate stats. */
void UpdateMaterialShaderCompilingStats(const FMaterial* Material)
{
	// ...
	
	if (ShadingModels.HasOnlyShadingModel(MSM_Unlit))
	{
		INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumUnlitMaterialShaders, 1);
	}
	else if (ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_Cloth, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_SingleLayerWater, MSM_ThinTranslucent, MSM_ToonLit }))	// TyT
	{
		INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumLitMaterialShaders, 1);
	}

	// ...
}

ShaderMaterial.h에서 struct FShaderMaterialPropertyDefines를 수정한다. 이부분은 뒤에 GBuffer 슬롯에 정의할때 사용된다.

struct FShaderMaterialPropertyDefines
{
	// ...
	
	uint8 MATERIAL_SHADINGMODEL_DEFAULT_LIT : 1;
	uint8 MATERIAL_SHADINGMODEL_SUBSURFACE : 1;
	uint8 MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN : 1;
	uint8 MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE : 1;
	uint8 MATERIAL_SHADINGMODEL_CLEAR_COAT : 1;
	uint8 MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE : 1;
	uint8 MATERIAL_SHADINGMODEL_HAIR : 1;
	uint8 MATERIAL_SHADINGMODEL_CLOTH : 1;
	uint8 MATERIAL_SHADINGMODEL_EYE : 1;
	uint8 MATERIAL_SHADINGMODEL_SINGLELAYERWATER : 1;
	uint8 SINGLE_LAYER_WATER_SEPARATED_MAIN_LIGHT : 1;
	uint8 MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT : 1;
	uint8 MATERIAL_SHADINGMODEL_TOON_LIT : 1;	// TyT
	
	// ...
}

MakeMaterialAttribute를 사용할때, 머티리얼은 셰이딩 모델을 선택하기위해 내부로직에서 이 노드를 사용한다. 따라서 MaterialExpressionShadingModel.h파일의 이 부분의 UPROPERTY(...)의 끝에 새 셰이딩모델을 추가해 준다.

UCLASS(collapsecategories, hidecategories = Object, MinimalAPI)
class UMaterialExpressionShadingModel : public UMaterialExpression
{
	GENERATED_UCLASS_BODY()

	//~ Begin UMaterialExpression Interface
#if WITH_EDITOR
	virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
	virtual uint32 GetOutputType(int32 OutputIndex) override;
	virtual void GetCaption(TArray<FString>& OutCaptions) const override;
	virtual bool GenerateHLSLExpression(FMaterialHLSLGenerator& Generator, UE::HLSLTree::FScope& Scope, int32 OutputIndex, UE::HLSLTree::FExpression const*& OutExpression) const override;
#endif
public:
	UPROPERTY(EditAnywhere, Category=ShadingModel,  meta=(ValidEnumValues="MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_Hair, MSM_Cloth, MSM_Eye, MSM_ToonLit", ShowAsInputPin = "Primary"))	// TyT
	TEnumAsByte<enum EMaterialShadingModel> ShadingModel = MSM_DefaultLit;
	//~ End UMaterialExpression Interface
}; 

여기까지 하면 이제 셰이딩 모델을 선택할 수 있게 된다.
이제 HLSLMaterialTranslator.cpp에서 FHLSLMaterialTranslator::GetMaterialEnvironment() 함수를 수정한다.
GetMaterialEnvironment()함수는 MaterialHLSLEmitter.cpp에도 있다. 이부분도 마찬가지로 추가해 준다.
(ShaderMaterial.h에서 추가한 enum과 TEXT가 일치함)

void FHLSLMaterialTranslator::GetMaterialEnvironment(EShaderPlatform InPlatform, FShaderCompilerEnvironment& OutEnvironment)
{
	bool bMaterialRequestsDualSourceBlending = false;
 
// ...

	// TyT
	if (ShadingModels.HasShadingModel(MSM_ToonLit))
	{
		OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_TOON_LIT"), TEXT("1"));
		NumSetMaterials++;
	}
	// TyT

// ...
}

ShaderGenerationUtil.cppFShaderCompileUtilities::ApplyFetchEnvironment()함수에서 HLSL define을 추가해 준다.
같은 파일에서, DetermineUsedMaterialSlots()함수에 새 셰이딩 모델을 추가하고, 엔진에 모델이 basic slots(base color, specular등)을 사용한다고 알려준다.
같은 파일에SetSlotsForShadingModelType()함수가 있는데, 이 함수는 현재 사용되지 않는 함수로 보인다. 하지만 일단은 수정해 준다.

void ShaderCompileUtilities::ApplyFetchEnvironment(FShaderMaterialPropertyDefines& SrcDefines, const FShaderCompilerEnvironment& Environment)
{
	FETCH_COMPILE_BOOL(MATERIAL_ENABLE_TRANSLUCENCY_FOGGING);
	FETCH_COMPILE_BOOL(MATERIALBLENDING_ANY_TRANSLUCENT);
	FETCH_COMPILE_BOOL(MATERIAL_USES_SCENE_COLOR_COPY);
	FETCH_COMPILE_BOOL(MATERIALBLENDING_MASKED_USING_COVERAGE);

	FETCH_COMPILE_BOOL(MATERIAL_COMPUTE_FOG_PER_PIXEL);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_UNLIT);

	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_DEFAULT_LIT);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_SUBSURFACE);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_CLEAR_COAT);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_HAIR);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_CLOTH);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_EYE);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_SINGLELAYERWATER);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT);
	FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_TOON_LIT); // TyT
	// ...
}

static void DetermineUsedMaterialSlots(
	bool Slots[],
	const FShaderMaterialDerivedDefines& Dst,
	const FShaderMaterialPropertyDefines& Mat,
	const FShaderLightmapPropertyDefines& Lightmap,
	const FShaderGlobalDefines& SrcGlobal,
	const FShaderCompilerDefines& Compiler,
	ERHIFeatureLevel::Type FEATURE_LEVEL)
{
	// ...
	// TyT
	if (Mat.MATERIAL_SHADINGMODEL_TOON_LIT)
	{
		SetStandardGBufferSlots(Slots, bWriteEmissive, bHasTangent, bHasVelocity, bHasStaticLighting, bIsStrataMaterial);
		Slots[GBS_CustomData] = bUseCustomData;
	}
	// TyT
}

static void SetSlotsForShadingModelType(bool Slots[], EMaterialShadingModel ShadingModel, bool bMergeCustom)
{
	switch (ShadingModel)
	{
	// ...
	// TyT
	case MSM_ToonLit:
		SetSharedGBufferSlots(Slots);
		if (bMergeCustom)
		{
			Slots[GBS_CustomData] = true;
		}
		else
		{
			Slots[GBS_SubsurfaceColor] = true;
			Slots[GBS_Opacity] = true;
		}
		break;
	// TyT
	}
}

Attachments/Pasted image 20240403150718.png
(함수 정의만 되어있는 모습)

Material.cpp에서 IsPropertyActive_Internal()함수에서, 아래 코드를 추가해 노드의 핀을 활성화 해 준다.

static bool IsPropertyActive_Internal(EMaterialProperty InProperty,
	EMaterialDomain Domain,
	EBlendMode BlendMode,
	FMaterialShadingModelField ShadingModels,
	ETranslucencyLightingMode TranslucencyLightingMode,
	bool bBlendableOutputAlpha,
	bool bUsesDistortion,
	bool bUsesShadingModelFromMaterialExpression,
	bool bIsTranslucencyWritingVelocity,
	bool bIsThinSurface,
	bool bIsSupported)
{
	//...
	case MP_SubsurfaceColor:
		Active = ShadingModels.HasAnyShadingModel({ MSM_Subsurface, MSM_PreintegratedSkin, MSM_TwoSidedFoliage, MSM_Cloth, MSM_ToonLit });	// TyT
		break;
	case MP_CustomData0:
		Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Hair, MSM_Cloth, MSM_Eye, MSM_SubsurfaceProfile, MSM_ToonLit });		// TyT
}

커스텀 데이터를 사용하기 위해 GBuffer쓰기 허용을 해준다.
MaterialShared.h

inline bool IsSubsurfaceShadingModel(FMaterialShadingModelField ShadingModel)
{
	return ShadingModel.HasShadingModel(MSM_Subsurface) || ShadingModel.HasShadingModel(MSM_PreintegratedSkin) ||
		ShadingModel.HasShadingModel(MSM_SubsurfaceProfile) || ShadingModel.HasShadingModel(MSM_TwoSidedFoliage) ||
		ShadingModel.HasShadingModel(MSM_Cloth) || ShadingModel.HasShadingModel(MSM_Eye) ||
		ShadingModel.HasShadingModel(MSM_ToonLit);	// TyT
}

ShaderMaterialDerivedHelpers.cpp

FShaderMaterialDerivedDefines RENDERCORE_API CalculateDerivedMaterialParameters(
	const FShaderMaterialPropertyDefines& Mat,
	const FShaderLightmapPropertyDefines& Lightmap,
	const FShaderGlobalDefines& SrcGlobal,
	const FShaderCompilerDefines& Compiler,
	ERHIFeatureLevel::Type FEATURE_LEVEL)
{
	// ...
	Dst.WRITES_CUSTOMDATA_TO_GBUFFER = (Dst.USES_GBUFFER && (Mat.MATERIAL_SHADINGMODEL_SUBSURFACE || Mat.MATERIAL_SHADINGMODEL_PREINTEGRATED_SKIN ||
		Mat.MATERIAL_SHADINGMODEL_SUBSURFACE_PROFILE || Mat.MATERIAL_SHADINGMODEL_CLEAR_COAT || Mat.MATERIAL_SHADINGMODEL_TWOSIDED_FOLIAGE ||
		Mat.MATERIAL_SHADINGMODEL_HAIR || Mat.MATERIAL_SHADINGMODEL_CLOTH || Mat.MATERIAL_SHADINGMODEL_EYE || Mat.MATERIAL_SHADINGMODEL_TOON_LIT));	// TyT
	// ...
}

이제 GBuffer에 새 셰이딩 모델이 쓰일 준비가 다 되었다.

번외: 핀 이름 변경하기(customdata0)

MaterialAttributeDefinitionMap.cpp파일에서, FMaterialAttributeDefinitionMap::GetAttributeOverrideForMaterial 함수를 수정한다.

FText FMaterialAttributeDefinitionMap::GetAttributeOverrideForMaterial(const FGuid& AttributeID, UMaterial* Material)
{
	// ...
	case MP_CustomData0:
	CustomPinNames.Add({ MSM_ClearCoat, "Clear Coat" });
	CustomPinNames.Add({ MSM_Hair, "Backlit" });
	CustomPinNames.Add({ MSM_Cloth, "Cloth" });
	CustomPinNames.Add({ MSM_Eye, "Iris Mask" });
	CustomPinNames.Add({ MSM_SubsurfaceProfile, "Curvature" });
	CustomPinNames.Add({ MSM_ToonLit, "ToonData" });	// TyT
	return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, "Custom Data 0"));
	// ...
}

HLSL

ShadingCommon.ush에서 define 코드를 추가해 준다.

// SHADINGMODELID_* occupy the 4 low bits of an 8bit channel and SKIP_* occupy the 4 high bits
#define SHADINGMODELID_UNLIT				0
#define SHADINGMODELID_DEFAULT_LIT			1
#define SHADINGMODELID_SUBSURFACE			2
#define SHADINGMODELID_PREINTEGRATED_SKIN	3
#define SHADINGMODELID_CLEAR_COAT			4
#define SHADINGMODELID_SUBSURFACE_PROFILE	5
#define SHADINGMODELID_TWOSIDED_FOLIAGE		6
#define SHADINGMODELID_HAIR					7
#define SHADINGMODELID_CLOTH				8
#define SHADINGMODELID_EYE					9
#define SHADINGMODELID_SINGLELAYERWATER		10
#define SHADINGMODELID_THIN_TRANSLUCENT		11
#define SHADINGMODELID_STRATA				12		// Temporary while we convert everything to Strata
// TyT
#define SHADINGMODELID_TOON_LIT				13		
// TyT
#define SHADINGMODELID_NUM					14		// TyT
#define SHADINGMODELID_MASK					0xF		// 4 bits reserved for ShadingModelID			

같은 파일에서, GetShadingModelColor()함수에 디버그 뷰 'Shading Model'을 볼때 표시될 컬러를 설정한다.

float3 GetShadingModelColor(uint ShadingModelID)
{
	// TODO: PS4 doesn't optimize out correctly the switch(), so it thinks it needs all the Samplers even if they get compiled out
	//	This will get fixed after launch per Sony...
#if PS4_PROFILE
		 if (ShadingModelID == SHADINGMODELID_UNLIT) return float3(0.1f, 0.1f, 0.2f); // Dark Blue
	else if (ShadingModelID == SHADINGMODELID_DEFAULT_LIT) return float3(0.1f, 1.0f, 0.1f); // Green
	else if (ShadingModelID == SHADINGMODELID_SUBSURFACE) return float3(1.0f, 0.1f, 0.1f); // Red
	else if (ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN) return float3(0.6f, 0.4f, 0.1f); // Brown
	else if (ShadingModelID == SHADINGMODELID_CLEAR_COAT) return float3(0.1f, 0.4f, 0.4f); 
	else if (ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE) return float3(0.2f, 0.6f, 0.5f); // Cyan
	else if (ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE) return float3(0.2f, 0.2f, 0.8f); // Blue
	else if (ShadingModelID == SHADINGMODELID_HAIR) return float3(0.6f, 0.1f, 0.5f);
	else if (ShadingModelID == SHADINGMODELID_CLOTH) return float3(0.7f, 1.0f, 1.0f); 
	else if (ShadingModelID == SHADINGMODELID_EYE) return float3(0.3f, 1.0f, 1.0f); 
	else if (ShadingModelID == SHADINGMODELID_SINGLELAYERWATER) return float3(0.5f, 0.5f, 1.0f);
	else if (ShadingModelID == SHADINGMODELID_THIN_TRANSLUCENT) return float3(1.0f, 0.8f, 0.3f);
	else if (ShadingModelID == SHADINGMODELID_STRATA) return float3(1.0f, 1.0f, 0.0f);
	else if (ShadingModelID == SHADINGMODELID_TOON_LIT) return float3(1.0f, 0.0f, 1.0f);	// TyT
	else return float3(1.0f, 1.0f, 1.0f); // White
#else
	switch(ShadingModelID)
	{
		case SHADINGMODELID_UNLIT: return float3(0.1f, 0.1f, 0.2f); // Dark Blue
		case SHADINGMODELID_DEFAULT_LIT: return float3(0.1f, 1.0f, 0.1f); // Green
		case SHADINGMODELID_SUBSURFACE: return float3(1.0f, 0.1f, 0.1f); // Red
		case SHADINGMODELID_PREINTEGRATED_SKIN: return float3(0.6f, 0.4f, 0.1f); // Brown
		case SHADINGMODELID_CLEAR_COAT: return float3(0.1f, 0.4f, 0.4f); // Brown
		case SHADINGMODELID_SUBSURFACE_PROFILE: return float3(0.2f, 0.6f, 0.5f); // Cyan
		case SHADINGMODELID_TWOSIDED_FOLIAGE: return float3(0.2f, 0.2f, 0.8f); // Cyan
		case SHADINGMODELID_HAIR: return float3(0.6f, 0.1f, 0.5f);
		case SHADINGMODELID_CLOTH: return float3(0.7f, 1.0f, 1.0f);
		case SHADINGMODELID_EYE: return float3(0.3f, 1.0f, 1.0f);
		case SHADINGMODELID_SINGLELAYERWATER: return float3(0.5f, 0.5f, 1.0f);
		case SHADINGMODELID_THIN_TRANSLUCENT: return float3(1.0f, 0.8f, 0.3f);
		case SHADINGMODELID_STRATA: return float3(1.0f, 1.0f, 0.0f);
		case SHADINGMODELID_TOON_LIT: return float3(1.0f, 0.0f, 1.0f);	// TyT
		default: return float3(1.0f, 1.0f, 1.0f); // White
	}
#endif
}

그리고, Definitions.usf파일에서 undefined define을 0으로 설정해 undefine일 경우, 코드에서 작동하지 않도록 설정한다.

// TyT
#ifndef MATERIAL_SHADINGMODEL_TOON_LIT
#define MATERIAL_SHADINGMODEL_TOON_LIT					0 
#endif
// TyT

머티리얼 값들을 GBuffer에 저장하는 함수를 수정한다.
ShadingModelsMaterial.ush파일에서 가장 아래에 새로운 #if문을 추가해 준다.

	// TyT
#if MATERIAL_SHADINGMODEL_TOON_LIT
	else if (ShadingModel == SHADINGMODELID_TOON_LIT)
	{
		GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor);
		GBuffer.CustomData.a = Opacity;	
	}
#endif
	// TyT

다음으로, DeferredShadingCommon.ush파일의 Getter함수에 새 셰이딩 모델을 추가해 주어야 한다. IsSubsurfaceModel()HasCustomGBufferData()에 추가해 준다.

bool IsSubsurfaceModel(int ShadingModel)
{
	return ShadingModel == SHADINGMODELID_SUBSURFACE 
		|| ShadingModel == SHADINGMODELID_PREINTEGRATED_SKIN 
		|| ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE
		|| ShadingModel == SHADINGMODELID_TWOSIDED_FOLIAGE
		|| ShadingModel == SHADINGMODELID_HAIR
		|| ShadingModel == SHADINGMODELID_EYE
		|| ShadingModel == SHADINGMODELID_TOON_LIT;	// TyT
}

bool HasCustomGBufferData(int ShadingModelID)
{
	return ShadingModelID == SHADINGMODELID_SUBSURFACE
		|| ShadingModelID == SHADINGMODELID_PREINTEGRATED_SKIN
		|| ShadingModelID == SHADINGMODELID_CLEAR_COAT
		|| ShadingModelID == SHADINGMODELID_SUBSURFACE_PROFILE
		|| ShadingModelID == SHADINGMODELID_TWOSIDED_FOLIAGE
		|| ShadingModelID == SHADINGMODELID_HAIR
		|| ShadingModelID == SHADINGMODELID_CLOTH
		|| ShadingModelID == SHADINGMODELID_EYE
		|| ShadingModelID == SHADINGMODELID_TOON_LIT;	// TyT
}

ShadingModels.ush파일의 IntegrateBxDF()함수에 새 셰이딩 모델의 라이팅을 어떻게 처리할지 코드를 추가해 준다. 여기에선 DefaultLit과 같은 라이팅 처리를 사용한다.

FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
	switch( GBuffer.ShadingModelID )
	{
		case SHADINGMODELID_DEFAULT_LIT:
		case SHADINGMODELID_SINGLELAYERWATER:
		case SHADINGMODELID_THIN_TRANSLUCENT:
		case SHADINGMODELID_TOON_LIT:		// TyT
			return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_SUBSURFACE:
			return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_PREINTEGRATED_SKIN:
			return PreintegratedSkinBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_CLEAR_COAT:
			return ClearCoatBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_SUBSURFACE_PROFILE:
			return SubsurfaceProfileBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_TWOSIDED_FOLIAGE:
			return TwoSidedBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_HAIR:
			return HairBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_CLOTH:
			return ClothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_EYE:
			return EyeBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		default:
			return (FDirectLighting)0;
	}
}

마지막으로, BasePassPixelShader.usf의 메인 함수인 void FPixelShaderInOut_MainPS()에 아래 코드를 추가해 준다.

void FPixelShaderInOut_MainPS(
	FVertexFactoryInterpolantsVSToPS Interpolants,
	FBasePassInterpolantsVSToPS BasePassInterpolants,
	in FPixelShaderIn In,
	inout FPixelShaderOut Out)
{
	// [...]
	
	float MaterialAO = GetMaterialAmbientOcclusion(PixelMaterialInputs);

	// If we don't use this shading model the color should be black (don't generate shader code for unused data, don't do indirectlighting cache lighting with this color).
	float3 SubsurfaceColor = 0;
	// 0..1, SubsurfaceProfileId = int(x * 255)
	float SubsurfaceProfile = 0;
	
	// TyT
#if MATERIAL_SHADINGMODEL_TOON_LIT
	float4 SubsurfaceData = GetMaterialSubsurfaceData(PixelMaterialInputs);
	if(ShadingModel = SHADINGMODELID_TOON_LIT)
	{
		SubsurfaceColor = SubsurfaceData.rgb;
		SubsurfaceProfile = SubsurfaceData.a;
	}	
#endif
	// TyT

	// [...]

	half3 DiffuseColor = 0;
	half3 Color = 0;
	float IndirectIrradiance = 0;

	half3 ColorSeparateSpecular = 0;
	half3 ColorSeparateEmissive = 0;

	float3 DiffuseIndirectLighting = 0;
	float3 SubsurfaceIndirectLighting = 0;

	float3 SeparatedWaterMainDirLightLuminance = float3(0, 0, 0); 

	// TyT	
#if MATERIAL_SHADINGMODEL_TOON_LIT
	Color = BaseColor;
#endif
	// TyT

	// [...]

결과

Attachments/Pasted image 20240403235425.png

만약 SubsurfaceColor가 정상적으로 나오지 않는다면, 아래의 GBuffer.CustomData.a = 1.0f로 수정하고 다시 리컴파일 해본다. 그리고 정상적으로 나오는지 확인
ShadingModelsMaterial.ush파일

	// TyT
#if MATERIAL_SHADINGMODEL_TOON_LIT
	else if (ShadingModel == SHADINGMODELID_TOON_LIT)
	{
		GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor);
		GBuffer.CustomData.a = 1.0f;	
	}
#endif
	// TyT