본문 바로가기
Development/UE4

UE4 Custom Shading Model

by LiiYuu 2020. 6. 15.

(Unreal Engine 4.25.1)

Default Lit을 카피한 Stylized Lit 이라는 Shading Model를 추가해보겠습니다.

 

 

EngineType.h

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"),
    /** 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
};

Material의 Shading Model 들을 정의하는 enum 입니다.

 

원하시는 Model 명을 MSM_NUM 위에 추가합니다.

    // ...
    MSM_ThinTranslucent			UMETA(DisplayName="Thin Translucent"),
    MSM_StylizedLit            UMETA(DisplayName="Stylized Lit"),
    MSM_NUM						UMETA(Hidden),​
    // ...

 

MaterialShader.cpp

/** 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_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;
		default: ShadingModelName = TEXT("Unknown"); break;
	}
	return ShadingModelName;
}

EMaterialShadingModel을 FString으로 바꿔주는 부분입니다.

MSM_ThinTranslucent 밑에 추가해줍니다.

// ...
case MSM_ThinTranslucent:	ShadingModelName = TEXT("MSM_ThinTranslucent"); break;
case MSM_StylizedLit:		ShadingModelName = TEXT("MSM_StylizedLit"); break;
default: ShadingModelName = TEXT("Unknown"); break;
// ...

모든 Material Shader의 컴파일 결과를 Stats에 반영하는 코드인 것 같습니다. :) ?

Stylized Lit 도 반영되도록 추가합니다.

void UpdateMaterialShaderCompilingStats(const FMaterial* Material)
{
	// ...
	else if (ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_Cloth, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_SingleLayerWater, MSM_ThinTranslucent, MSM_StylizedLit }))
	{
		INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumLitMaterialShaders, 1);
	}
	// ...
}

 

Material.cpp

static bool IsPropertyActive_Internal(EMaterialProperty InProperty, /* ... */ )

모든 Shading Model 들은 이 함수를 호출하여 활성화할 Material Property를 결정합니다.

Default Lit 이 활성화하는 속성을 Stylized Lit 도 활성화하도록 수정합니다.

switch (InProperty)
{
// ...
case MP_Anisotropy:
	Active = bAnisotropicBRDF && ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_ClearCoat, MSM_StylizedLit }) && (!bIsTranslucentBlendMode || !bIsVolumetricTranslucencyLightingMode);
	break;
// ...
case MP_Tangent:
	Active = bAnisotropicBRDF && ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_ClearCoat, MSM_StylizedLit }) && (!bIsTranslucentBlendMode || !bIsVolumetricTranslucencyLightingMode);
	break;
// ...
}

이제 Material 에디터에서 Stylized Lit을 선택하시면 Anisotropy 와 Tangent 속성이 UI 에 표시됩니다.

 

추가적으로, Stylized Lit은 Default Lit을 기반으로하기 때문에 해야할 작업이 약간 있습니다.

int32 FMaterialResource::CompilePropertyAndSetMaterialProperty(EMaterialProperty Property, FMaterialCompiler* Compiler, EShaderFrequency OverrideShaderFrequency, bool bUsePreviousFrameTime) const

기본적으로 표면이 불투명인 경우 masked/translucent 전용 속성(ex: opacity, opacity mask)들은 스킵합니다.

일부 기능들은 실수로 이 코드를 실행하여 마스킹된 버전의 이상한 Material을 만들 수 있기 때문입니다.

(즉, 불투명 Material이 투명 속성 을 적용 받으면 안되는데 실수로 받아버려서 이상하게 렌더링 되는 문제를 방지하는 코드인 듯?합니다.)

DefulatLit 뒤에 StylizedLit을 추가합니다

switch(Property)
{
	// ...
	case MP_Opacity:
	case MP_OpacityMask:
		// Force basic opaque surfaces to skip masked/translucent-only attributes.
		// Some features can force the material to create a masked variant which unintentionally runs this dormant code
		if (GetMaterialDomain() != MD_Surface || GetBlendMode() != BLEND_Opaque || (GetShadingModels().IsLit() && !(GetShadingModels().HasShadingModel(MSM_DefaultLit) || GetShadingModels().HasShadingModel(MSM_StylizedLit)))
			|| GetShadingModels().HasShadingModel(MSM_SingleLayerWater))
		{
			Ret = MaterialInterface->CompileProperty(Compiler, Property);
		}
		else
		{
			Ret = FMaterialAttributeDefinitionMap::CompileDefaultExpression(Compiler, Property);
		}
		break;
	// ...
};

아 생각해보니까 Material Domain이 Surface 가 아닌 것 중에서(like Post Process, ...), Stylized Lit을 이용하는일은 없을 것 이니 빼도 됩니다... ;)

 

자. 이제 쉐이더에 Stylized Lit이 추가 됬음을 알려야합니다.

그리고 쉐이더에서 확인할 수 있도록 수정해야합니다.

 

HLSLMaterialTranslator.cpp

쉐이더의 머테리얼 환경을 설정해주는 곳 입니다.

void FHLSLMaterialTranslator::GetMaterialEnvironment(EShaderPlatform InPlatform, FShaderCompilerEnvironment& OutEnvironment)
if (ShadingModels.IsLit())
{	
	// ...
	if (ShadingModels.HasShadingModel(MSM_StylizedLit))
	{
		OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_STYLIZED_LIT"), TEXT("1"));
		NumSetMaterials++;
	}
	// ...
}

 

이제 머테리얼의 Shading Model이 Stylized Lit 으로 설정된다면, HLSL 컴파일러가 MATERIAL_SHADINGMODEL_STYLIZED_LIT를 전처리로 정의 할 것 입니다.

(#define MATERIAL_SHADINGMODEL_STYLIZED_LIT 1)

이것으로 나중에 쉐이더 코드에서 #if MATERIAL_SHADINGMODEL_STYLIZED_LIT 를 통해 Stylized lit 모델에서 작동하도록 만들 수 있습니다.

 

자 이제 쉐이더 차례입니다.

 

 

Definitions.usf

// ...
#ifndef MATERIAL_SHADINGMODEL_UNLIT
#define	MATERIAL_SHADINGMODEL_UNLIT						0
#endif

#ifndef MATERIAL_SHADINGMODEL_STYLIZED_LIT
#define	MATERIAL_SHADINGMODEL_STYLIZED_LIT				0
#endif

#ifndef MATERIAL_SINGLE_SHADINGMODEL
#define	MATERIAL_SINGLE_SHADINGMODEL					0
#endif
// ...

 

 

ShadingCommon.ush

머테리얼 빌드 시 지정한 Shading Model을 쉐이더에서 치환을 통해 해당 Shading Model을 사용합니다.

DeferredLightPixelShader 에서 조명 연산 시 각 픽셀마다 다른 라이팅 연산을 적용할 수 있도록 BasePass 단계에서 GBuffer에 Shading ID 를 저장합니다. (SetGBufferForShadingModel 참조.)

먼저 새로운 Shaind Model ID 전처리를 추가하고 SHADINGMODELID_NUM 을 변경합니다.

// ...
#define SHADINGMODELID_THIN_TRANSLUCENT		11
#define SHADINGMODELID_STYLIZED_LIT			12
#define SHADINGMODELID_NUM					13
// ...

Visualize 디버깅시 표시할 색상을 지정해줍니다.

// for debugging and to visualize
float3 GetShadingModelColor(uint ShadingModelID)
{
	// ...
#if PS4_PROFILE
	// ...
	else if (ShadingModelID == SHADINGMODELID_THIN_TRANSLUCENT) return float3(1.0f, 0.8f, 0.3f);
	else if (ShadingModelID == SHADINGMODELID_STYLIZED_LIT) return float3(0.25f, 0.25f, 0.25f);
	else return float3(1.0f, 1.0f, 1.0f); // White
#else
	switch(ShadingModelID)
	{
		// ...
        case SHADINGMODELID_THIN_TRANSLUCENT: return float3(1.0f, 0.8f, 0.3f);
		case SHADINGMODELID_STYLIZED_LIT: return float3(0.25f, 0.25f, 0.25);
		default: return float3(1.0f, 1.0f, 1.0f); // White
	}
#endif
}

에디터 뷰포트에서 View Mode를 Buffer Visualization - Shading Model 로 선택하시면 현재 픽셀에 사용하고있는 Shading Model ID의 컬러를 확인 할 수 있습니다.

 

ShadingModels.ush

GBuffer의 데이터를 기반으로 조명 연산단계에서 사용할 함수를 선택합니다.

Default Lit의 조명 연산을 사용하기 위해 밑에 추가합니다.

FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, float NoL, FAreaLight AreaLight, FShadowTerms Shadow )
{
	switch( GBuffer.ShadingModelID )
	{
		case SHADINGMODELID_DEFAULT_LIT:
		case SHADINGMODELID_SINGLELAYERWATER:
		case SHADINGMODELID_THIN_TRANSLUCENT:
		case SHADINGMODELID_STYLIZED_LIT:
			return DefaultLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		case SHADINGMODELID_SUBSURFACE:
			return SubsurfaceBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );
		// ...
	}
}

 

BasePassPixelShader.usf

Default Lit 처럼 반투명 조명 연산을 지원하기 위해 추가합니다.

// Volume lighting for lit translucency
#if (MATERIAL_SHADINGMODEL_DEFAULT_LIT || MATERIAL_SHADINGMODEL_SUBSURFACE || SHADINGMODELID_STYLIZED_LIT) && (MATERIALBLENDING_TRANSLUCENT || MATERIALBLENDING_ADDITIVE) && !SIMPLE_FORWARD_SHADING && !FORWARD_SHADING
	if (GBuffer.ShadingModelID == SHADINGMODELID_DEFAULT_LIT || GBuffer.ShadingModelID == SHADINGMODELID_SUBSURFACE)
	{
		Color += GetTranslucencyVolumeLighting(MaterialParameters, PixelMaterialInputs, BasePassInterpolants, GBuffer, IndirectIrradiance);
	}
#endif