반응형

Unreal Engine에 대해서 좀 더 깊은 공부를 하고 싶다는 생각에, 무지성으로 시작해보는 공부이다.

Shading Model을 추가해보고, Shader Code에 대해서 좀 더 다양하게 사용해보는 것을 목표로 하려한다.

 

개인적으로 존경할만한 프로그래머분인 Canny라는 분께서 제작하신, Canny Engine을 보고 삘받아서 공부해본다.


시작하기 앞서, Shader 작업을 하게되면, Engine Build 시간이 오래 걸리기 때문에, Shader Compile Commands를 알고 있는 것이 도움이 될 것이다.

RecompileShaders changed
변경된 Shader를 다시 Compile 한다.

RecompileShaders all
Editor를 열 때와 동일하게 Compile 한다.

RecompileShaders global 
모든 .usf 파일을 다시 Compile 한다.

RecompileShaders material [name]
특정 Material만 다시 Compile 한다.

RecompileShaders platform [name]
특정 Platform만 다시 Compile 한다.

Shader 공부할 때, 가장 기본이 되는 Cell Shader, 흔히 말하는 카툰 셰이딩을 추가해보도록 한다.

Unreal Engine을 사용해보면 쉽게 접하는 Material 에셋을 통하여 기본 구성 요소를 검색하며 접근해보았다.

 

 

Shading Model

추가하려는 Shading Model을 확인 할 수 있으며, 위의 Shading Model을 추가하여 필요한 Material Parameter를 통해 쉽게 사용하는 방식으로 기능 구현이 된다.

 

위의 Shading Model Type들을 검색하여 찾으면, EMaterialShadingModel 을 쉽게 찾을 수 있다.

UENUM()
enum EMaterialShadingModel
{
	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="Strata", Hidden),
	/** 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
};

위의 코드를 보면, 아 여기에 Cell Shading Model을 추가하면 된다를 알 수 있다.

 

좀 더 확장하여 찾아보면, DecodeShadingModel, PixelInspector 등 다양한 곳에 Enum Type을 사용하는데, 

깨부로 하면 정말 오랜 시간이 걸릴거라 생각하여, Tutorial을 참고하여 진행해보기로 했다..


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

 

 

위의 링크 순서로 하면,
1. Cell Shading 관련 설정 후
2. Shading Model을 추가하는 방식이라,

공부하는 데에는 Model을 먼저 추가한 후, Cell Shading을 설정하는 방향이 이해가 잘 될 듯하여, 임의로 Model부터 추가하는 작업으로 진행해보려고 한다.

 

Model을 추가하는 방법은 위에서 찾아본 대로, EMaterialShadingModel에 Custom Model을 추가하면 된다.

// EngineType.h

/** 
 * Specifies the overal rendering/shading model for a material
 * @warning Check UMaterialInstance::Serialize if changed!
 */
UENUM()
enum EMaterialShadingModel
{
	~~ ... ~~
	MSM_Strata					UMETA(DisplayName="Strata", Hidden),
	/** 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"),
	/** DARU :> Cell Shading */
	MSM_DR_CellShading			UMETA(Displayname="Cell Shading"), 
	MSM_MAX
};

테스트 모델인 MSM_DR_CellShading을 가장 마지막 Model인 From Material Expression 뒤에 추가를 하였다.

위의 주석을 확인해보니, 변경 사항이 생기면 UMaterialInstance::Serialze를 확인해보라고 한다.

 

코드를 확인해보니, 

// MaterialInstance.cpp

void UMaterialInstance::Serialize(FArchive& Ar)
{
	~~ ... ~~

	if (Ar.UEVer() >= VER_UE4_MATERIAL_INSTANCE_BASE_PROPERTY_OVERRIDES )
	{
#if WITH_EDITORONLY_DATA
		if( Ar.UEVer() < VER_UE4_FIX_MATERIAL_PROPERTY_OVERRIDE_SERIALIZE )
		{
			// awful old native serialize of FMaterialInstanceBasePropertyOverrides UStruct
			Ar << bOverrideBaseProperties_DEPRECATED;
			bool bHasPropertyOverrides = false;
			Ar << bHasPropertyOverrides;
			if( bHasPropertyOverrides )
			{
				FArchive_Serialize_BitfieldBool(Ar, BasePropertyOverrides.bOverride_OpacityMaskClipValue);
				Ar << BasePropertyOverrides.OpacityMaskClipValue;

				if( Ar.UEVer() >= VER_UE4_MATERIAL_INSTANCE_BASE_PROPERTY_OVERRIDES_PHASE_2 )
				{
					FArchive_Serialize_BitfieldBool(Ar, BasePropertyOverrides.bOverride_BlendMode);
					Ar << BasePropertyOverrides.BlendMode;
					FArchive_Serialize_BitfieldBool(Ar, BasePropertyOverrides.bOverride_ShadingModel);
					Ar << BasePropertyOverrides.ShadingModel;
					FArchive_Serialize_BitfieldBool(Ar, BasePropertyOverrides.bOverride_TwoSided);
					FArchive_Serialize_BitfieldBool(Ar, BasePropertyOverrides.TwoSided);

					if( Ar.UEVer() >= VER_UE4_MATERIAL_INSTANCE_BASE_PROPERTY_OVERRIDES_DITHERED_LOD_TRANSITION )
					{
						FArchive_Serialize_BitfieldBool(Ar, BasePropertyOverrides.bOverride_DitheredLODTransition);
						FArchive_Serialize_BitfieldBool(Ar, BasePropertyOverrides.DitheredLODTransition);
					}
					// unrelated but closest change to bug
					if( Ar.UEVer() < VER_UE4_STATIC_SHADOW_DEPTH_MAPS )
					{
						// switched enum order
						switch( BasePropertyOverrides.ShadingModel )
						{
							case MSM_Unlit:			BasePropertyOverrides.ShadingModel = MSM_DefaultLit; break;
							case MSM_DefaultLit:	BasePropertyOverrides.ShadingModel = MSM_Unlit; break;
						}
					}
				}
			}
		}
#endif
	}

	~~ ... ~~
}

추측하건데, 아마 UE4 이하 버전에서는 Shading Model을 따로 Serialize를 해주어야 하기 때문에, 참고하라고 되어있는 듯 하다.

 

현재 필자는 UE5를 사용하고 있고, 다른 버전에 대한 예외 처리에 대한 부분은 고려하지 않고 공부에 초점을 맞추고 있기 때문에, 무시한 채로 진행하겠다.

(다른 Enum Type들도 되어있지 않으니, 괜찬을 것이라고 판단한다.)

 

// MaterialShader.cpp

/** Converts an EMaterialShadingModel to a string description. */
FString GetShadingModelString(EMaterialShadingModel ShadingModel)
{
	FString ShadingModelName;
	switch(ShadingModel)
	{
		~~ ... ~~
        
		case MSM_ThinTranslucent:	ShadingModelName = TEXT("MSM_ThinTranslucent"); break;
		case MSM_DR_CellShading:	ShadingModelName = TEXT("MSM_DR_CellShading"); break;
		default: ShadingModelName = TEXT("Unknown"); break;
	}
	return ShadingModelName;
}

마찬가지로 Enum Shading Model을 String으로 변환해주는 함수에 같이 추가한다.

 

// MaterialShader.cpp

/** 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_DR_CellShading }))
	{
		INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumLitMaterialShaders, 1);
	}

	~~ ... ~~
}

같은 파일 내에서 Shading Model Compile을 위한 코드로 Has Any Shading Model의 Set Parameter의 끝에, Custom Shading Model을 추가해준다.


GBuffer 내용 - 미리 작업해도 상관 없으나, 공부 순서로 인해 이후에 GBuffer Slot 관련 작업 진행할 때, 다시 작업 예정

더보기

나중에 사용될 예정이지만, 추후 GBuffer 슬롯을 정의하는데 사용될 내용도 미리 선언을 해두었다.

아마 당장은 사용되지 않으므로, 주석으로 작업 후, 추후 GBuffer 관련 내용을 진행할 때 사용하도록 하겠다.

(기본 필수 요소가 아닌, 옵션에 해당되는 부분으로 판단되어 혼동 방지를 위함)

// ShaderMaterial.h

struct FShaderMaterialPropertyDefines
{
	~~ ... ~~
    
	uint8 MATERIAL_COMPUTE_FOG_PER_PIXEL : 1;
	uint8 MATERIAL_SHADINGMODEL_UNLIT : 1;

	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_DF_SHADOW_ENABLED : 1;
	uint8 MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT : 1;
    // TODO :> 추후 GBuffer 구현 시 사용 예정
	// uint8 MATERIAL_SHADINGMODEL_DR_CELL_SHADING : 1;

	~~ ... ~~
};

// MaterialExpressionShadingModel.h

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_DR_CellShading", ShowAsInputPin = "Primary"))
	TEnumAsByte<enum EMaterialShadingModel> ShadingModel = MSM_DefaultLit;
	//~ End UMaterialExpression Interface
};

Shading Model을 선택하는 옵션은 UPROPERTY 매크로를 통하여, 선택 범위를 제한해두었기 때문에, ValidEnumValues 옵션 뒤에 Custom Shading Model을 추가해주어야 한다.

 

위의 작업까지 진행하면 Shading Model이 추가되어야 한다.

Build 후, Material Asset의 Shading Model을 확인해보자. (Engine Build 시간이 길기 때문에, 넘겨도 무방하다.)

Add Custom Shading Model (Cell Shading)

EMaterialShadingModel에 추가된 이름으로 주석이 설정되어, 추가된 것을 확인할 수 있다.

 

선택을 하면, Crash가 발생하는데, 

void AddShadingModel(EMaterialShadingModel InShadingModel)	
{ check(InShadingModel < MSM_NUM); ShadingModelField |= (1 << (uint16)InShadingModel); }

해당 로직에서 발생한다.

 

아마 원인은, MSM_Num은 MSM_Strata 까지만을 허용하는 듯하다.

추가한 Custom Shading Model (MSM_DR_CellShading)을 MSM_Strata 아래로 (MSM_NUM 위로) 옮겨야 한다.

 

그 이후에 다시 Build 후, 선택 하면 ensure는 발생하지만 정상적으로 변경되는 것을 확인할 수 있다.

Custom Shading Selected

반응형

+ Recent posts