[Unreal_Rendering]ToonShading

2024. 6. 18. 23:54unreal/UnrealRendering

 공개 가능한 PV영상

https://www.youtube.com/watch?v=FeMHBmp7ZqY&ab_channel=%EB%B8%94%EB%9E%99%EC%8A%A4%ED%86%B0

 

 

 

언리얼 5.4 기준

 

인터넷에 단순히 쉐이딩 모델 추가하고 카툰 쉐이딩을 만드는 방법자체는 많이 나와있는데

추가해야되고 어떤부분때문에 추가해야하는지 설명한건 보기 힘든것 같아

엔진 업데이트를 하더라도 스스로 알아서 수정할 있도록 상세 내역을 주석을 달아 적었습니다.

따라서 내용 자체가 처음보시는 분들은 가독성이 떨어질 있지만 쉐이딩 모델을 추가해보신 분이라면

알수 있는 정도의 내용입니다.

 

 

선택사항

  • 기본 디파인 추가

실제 프로젝트에서 엔진을 수정할때는 디파인을 통해 기능자체를 꺼거나 있도록 하는 편이 좋습니다.

실제 소스나 쉐이더파일에서 디파인을 통해 끄거나키고

디파인이 되지 않는 프로퍼티 등록 등은 메모를 남겨 수동으로 키거나 꺼줘야 합니다(Enginetype 같은경우)

 

CoreDefine.h

소스파일쪽의 디파인입니다.

#define MY_CUSTOM_SHADER 1

 

이후 쉐이더에서 사용할 디파인도 추가해줍니다.

 

플랫폼별 쉐이더에 디파인 추가(쉐이더용 디파인)

 

메탈 플랫폼용

MetalShaderFormat.cpp

virtual void ModifyShaderCompilerInput(FShaderCompilerInput& Input) const override

{

Input.Environment.SetDefine(TEXT("MyCustom_CUSTOM_SHADER_EDIT"), 1);

// Work out which standard we need, this is dependent on the shader platform.

// TODO: Read from toolchain class

const bool bIsMobile = FMetalCompilerToolchain::Get()->IsMobile((EShaderPlatform)Input.Target.Platform);

if (bIsMobile)

Input.Environment.SetDefine(TEXT("MyCustom_CUSTOM_SHADER_EDIT"), 1);

 

오픈지엘 플랫폼용

ShaderFormatOpengl.cpp

virtual void ModifyShaderCompilerInput(FShaderCompilerInput& Input) const override

{

GLSLVersion Version = TranslateFormatNameToEnum(Input.ShaderFormat);

switch (Version)

{

 

Input.Environment.SetDefine(TEXT("MyCustom_CUSTOM_SHADER_EDIT"), 1);

 

불칸 플랫폼용

VulkanShaderCompiler.cpp

{

FVulkanShaderCompilerInternalState InternalState(Input, nullptr);

Input.Environment.SetDefine(TEXT("COMPILER_HLSLCC"), 1);

Input.Environment.SetDefine(TEXT("COMPILER_VULKAN"), 1);

 

Input.Environment.SetDefine(TEXT("MyCustom_CUSTOM_SHADER_EDIT"), 1);

 

윈도우

ShaderFormatD3D.cpp

void ModifyShaderCompilerInput(FShaderCompilerInput& Input) const final

{

const ELanguage Language = LanguageFromFormat(Input.ShaderFormat);

const ED3DShaderModel ShaderModel = DetermineShaderModel(Input, Language);

 

Input.Environment.SetDefine(TEXT("MyCustom_CUSTOM_SHADER_EDIT"), 1);

 

 

소스파일 수정

  • 쉐이딩 모델 추가

2개까지는 그냥 추가 해도되나 3개부터는 최대수 제한에 걸리게됩니다.

비트수를 늘려서 추가해도되나 안쓰는 strata 를 제거한뒤에 추가 가능

 

쉐이더에서 사용할 머터리얼및 관련 파라메터를 등록해줍니다.

쉐이딩모델 종류 / 지버퍼 사용량 / 라이팅 연산 여부 등이 있고

컴파일쪽에 전달해줘서 이 머터리얼이 컴파일을 하는것이다 라고 명시 해주어야 합니다.

 

 

  • EngineTypes.h

하단 리스트에 추가 다른 쉐이더 선언 아래 붙여서 만들어주시면됩니다.

 

//MSM_Strata                                        UMETA(DisplayName = "Substrate", Hidden),

MSM_MyCustom_Default                        UMETA(DisplayName = "NB Character Default"),

MSM_MyCustom_Face                                UMETA(DisplayName = "NB Character Face"),

MSM_MyCustom_Smooth                        UMETA(DisplayName = "NB Character Smooth"),

 

 

 

 

  • ShaderMaterial.h

 

FShaderMaterialPropertyDefines 구조체 내부에 커스텀 머터리얼 사용 설정

 

struct FShaderMaterialPropertyDefines

{

//DECLARE_TYPE_LAYOUT(FShaderMaterialPropertyDefines, NonVirtual);

 

//void ModifyEnvironment(FShaderCompilerEnvironment& OutEnvironment) const;

//void WriteFrozenVertexFactoryParameters(FMemoryImageWriter& Writer, const TMemoryImagePtr<FShaderMaterialPropertyDefines>& InPropDefines) const;

#if MyCustom_CUSTOM_SHADER

uint8 MATERIAL_SHADINGMODEL_NBCHAR_DEFAULT : 1;

uint8 MATERIAL_SHADINGMODEL_NBCHAR_FACE : 1;

uint8 MATERIAL_SHADINGMODEL_NBCHAR_SMOOTH : 1;

 

 

머터리얼의 쉐이딩 모델 id 등록

이전버전에서는 다른곳에 있던 머터리얼bitmask id(쉐이딩모델id) 등록과정인데 위치가 변경되었습니다.

 

  • PixelInspectorResult.h

class FFloat16Color;

 

#define PIXEL_INSPECTOR_SHADINGMODELID_UNLIT 0

#define PIXEL_INSPECTOR_SHADINGMODELID_DEFAULT_LIT 1

#define PIXEL_INSPECTOR_SHADINGMODELID_SUBSURFACE 2

#define PIXEL_INSPECTOR_SHADINGMODELID_PREINTEGRATED_SKIN 3

#define PIXEL_INSPECTOR_SHADINGMODELID_CLEAR_COAT 4

#define PIXEL_INSPECTOR_SHADINGMODELID_SUBSURFACE_PROFILE 5

#define PIXEL_INSPECTOR_SHADINGMODELID_TWOSIDED_FOLIAGE 6

#define PIXEL_INSPECTOR_SHADINGMODELID_HAIR 7

#define PIXEL_INSPECTOR_SHADINGMODELID_CLOTH 8

#define PIXEL_INSPECTOR_SHADINGMODELID_EYE 9

#define PIXEL_INSPECTOR_SHADINGMODELID_SINGLELAYERWATER 10

#define PIXEL_INSPECTOR_SHADINGMODELID_THIN_TRANSLUCENT 11

 

#if MyCustom_CUSTOM_SHADER

#define PIXEL_INSPECTOR_SHADINGMODELID_MyCustom_DEFAULT 12

#define PIXEL_INSPECTOR_SHADINGMODELID_ MyCustom _FACE 13

#define PIXEL_INSPECTOR_SHADINGMODELID_ MyCustom _SMOOTH 14

#else

 

  • PixelInspectorResult.cpp

스위치 문내에 포함 - 3개이상 모델을 추가했을시 삭제된 모델을 빼주어야 합니다.

EMaterialShadingModel PixelInspectorResult::DecodeShadingModel(float InPackedChannel)

{

int32 ShadingModelId = ((uint32)FMath::RoundToInt(InPackedChannel * (float)0xFF)) & PIXEL_INSPECTOR_SHADINGMODELID_MASK;

switch (ShadingModelId)

{

#if MyCustom_CUSTOM_SHADER

case PIXEL_INSPECTOR_SHADINGMODELID_NBCHAR_DEFAULT:

return EMaterialShadingModel::MSM_ MyCustom _Default;

case PIXEL_INSPECTOR_SHADINGMODELID_NBCHAR_FACE:

return EMaterialShadingModel::MSM_ MyCustom _Face;

case PIXEL_INSPECTOR_SHADINGMODELID_NBCHAR_SMOOTH:

return EMaterialShadingModel::MSM_ MyCustom r_Smooth;

#else

case PIXEL_INSPECTOR_SHADINGMODELID_SUBSTRATE:

return EMaterialShadingModel::MSM_Strata;

#endif

 

지버퍼의 커스텀 데이터 세팅을 사용한다면 아래 부분도 세팅해주어야 합니다.

 

void PixelInspectorResult::DecodeCustomData(FVector4 InCustomData)

{

switch (ShadingModel)

{

case EMaterialShadingModel::MSM_Unlit:

case EMaterialShadingModel::MSM_DefaultLit:

case EMaterialShadingModel::MSM_SingleLayerWater:

case EMaterialShadingModel::MSM_ThinTranslucent:

#if MyCustom_CUSTOM_SHADER

예를 들면 my model 이란 쉐이딩 모델을 추가했고 거기에 커스텀 데이터를 쓴다면

아래와같이 케이스 추가

case EMaterialShadingModel::MSM_MyModel1:

case EMaterialShadingModel::MSM_MyModel2:

case EMaterialShadingModel::MSM_MyModel3:

아래와같이 디코딩할 내용 추가

{

MycustomColor = DecodeSubSurfaceColor(EncodedSubSurfaceColor);

MyCustomAlpha = InCustomData.W;

}

break;

 

#else

case EMaterialShadingModel::MSM_Strata:

#endif

 

  • 사용기능 등록 컴파일러 등록
  • ShaderGenerationUtil.cpp

 

컴파일러에 이 머터리얼을 컴파일 해야한다고 등록을 합니다

template<typename EnvironmentType>

void ApplyFetchEnvironmentInternal(FShaderMaterialPropertyDefines& SrcDefines, const EnvironmentType& Environment)

{

 

#if MyCustom_CUSTOM_SHADER

FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_MyCustom _DEFAULT);

FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_MyCustom _FACE);

FETCH_COMPILE_BOOL(MATERIAL_SHADINGMODEL_MyCustom _SMOOTH);

------

 

지버퍼 슬롯 세팅

 

static void SetSlotsForShadingModelType(bool Slots[], EMaterialShadingModel ShadingModel, bool bMergeCustom)

{

switch (ShadingModel)

{

#if MyCustom_CUSTOM_SHADER

case MSM_MyCustom_Default:

case MSM_MyCustom_Face:

case MSM_MyCustom_Smooth:

SetSharedGBufferSlots(Slots);

break;

 

------

지버퍼 슬롯 세팅

 

static void DetermineUsedMaterialSlots(

EGBufferSlotUsage Slots[GBS_Num],

const FShaderMaterialDerivedDefines& Dst,

const FShaderMaterialPropertyDefines& Mat,

const FShaderLightmapPropertyDefines& Lightmap,

const FShaderGlobalDefines& SrcGlobal,

const FShaderCompilerDefines& Compiler,

ERHIFeatureLevel::Type FEATURE_LEVEL)

 

#if MyCustom_CUSTOM_SHADER

if (Mat.MATERIAL_SHADINGMODEL_NBCHAR_DEFAULT)

{

현재 기본 지버퍼 세팅을 사용할것이기에 스탠다드로 진행하였지만 확장지버퍼(D) 사용한다면 서브서페이스쪽이랑 같은형식으로 설정해주면 됩니다.

SetStandardGBufferSlots(Slots, bWriteEmissive, bHasTangent, bHasVelocity, bWritesVelocity, bHasStaticLighting, bIsSubstrateMaterial);

//Slots[GBS_CustomData] = true;

}

 

 

  • 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;

#if MyCustom_CUSTOM_SHADER

case MSM_MyCustom_Default:        ShadingModelName = TEXT("MSM_MyCustom_Default"); break;

case MSM_MyCustom_Face:                ShadingModelName = TEXT("MSM_ MyCustom_Face"); break;

case MSM_MyCustom_Smooth:                ShadingModelName = TEXT("MSM_MyCustom_Smooth"); break;

#endif

 

-----------

 

릿 쉐이더라고 명시해주는 부분입니다. 라이팅 연산시 확인하는 비트입니다.

 

/** Called for every material shader to update the appropriate stats. */

void UpdateMaterialShaderCompilingStats(const FMaterial* Material)

{

INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumTotalMaterialShaders,1);

 

switch(Material->GetBlendMode())

{

case BLEND_Opaque: INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumOpaqueMaterialShaders,1); break;

case BLEND_Masked: INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumMaskedMaterialShaders,1); break;

default: INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumTransparentMaterialShaders,1); break;

}

FMaterialShadingModelField ShadingModels = Material->GetShadingModels();

 

if (ShadingModels.HasOnlyShadingModel(MSM_Unlit))

{

INC_DWORD_STAT_BY(STAT_ShaderCompiling_NumUnlitMaterialShaders, 1);

}

코드내용이 쉐이더 컴파일러에게 릿인지 언릿인지 알려주는 과정입니다. 디폴트 릿이 있는곳에 포함시켜줍니다.

#if MyCustom_CUSTOM_SHADER

else if (ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_Cloth, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_SingleLayerWater, MSM_ThinTranslucent, MSM_ MyCustom_Default , MSM_ MyCustom_Face , MSM_ MyCustom_Smooth }))

#else

else if (ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, MSM_Subsurface, MSM_PreintegratedSkin, MSM_ClearCoat, MSM_Cloth, MSM_SubsurfaceProfile, MSM_TwoSidedFoliage, MSM_SingleLayerWater, MSM_ThinTranslucent }))

#endif

 

----------------------------

 

  • HLSLMaterialTranslator.cpp

 

아래 함수에서 머터리얼의 설정을 진행합니다 여기에도 추가해 줍니다.

비슷한 내용이 여기저기 많은데

 FHLSLMaterialTranslator::GetMaterialEnvironment 함수는 우리가 에디터에서 사용하는 머터리얼의 기능을 실제 hlsl코드로 변환하는 내용이며

아래 코드의 내용은 현재 변환할 머터리얼이 msm_mycustom_** 라면 쉐이더코드에

#Define MATERIAL_SHADINGMODEL_mycustom_DEFAULT 1

이라는 줄을 삽입하라는 내용입니다.

 

void FHLSLMaterialTranslator::GetMaterialEnvironment(EShaderPlatform InPlatform, FShaderCompilerEnvironment& OutEnvironment)

{

#if MyCustom_CUSTOM_SHADER

if (EnvironmentDefines->HasShadingModel(MSM_ MyCustom_Default))

{

OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_ MyCustom_DEFAULT"), TEXT("1"));

 

}

#endif

 

 

사용 기능 설정(지버퍼)

 

  • ShaderMaterialDerivedHelpers.cpp

하위 커스텀 데이터핀을 지버퍼로 넘길지 결정해줍니다. 커스텀 데이터를 옮기기 위해선 필수

모바일 gl에서는 메모리 제한때문에 지버퍼 확장을 사용할 수 없으므로 남는 공간에 넣어줘야 합니다.

그래도 일단 밑작업은 진행해 줍니다.

 

FShaderMaterialDerivedDefines RENDERCORE_API CalculateDerivedMaterialParameters(

const FShaderMaterialPropertyDefines& Mat,

const FShaderLightmapPropertyDefines& Lightmap,

const FShaderGlobalDefines& SrcGlobal,

const FShaderCompilerDefines& Compiler,

ERHIFeatureLevel::Type FEATURE_LEVEL)

{

FShaderMaterialDerivedDefines Dst = {};

 

// Translucent materials need to compute fogging in the forward shading pass

// Materials that read from scene color skip getting fogged, because the contents of the scene color lookup have already been fogged

// This is not foolproof, as any additional color the material adds will then not be fogged correctly

 

커스텀 데이터 핀의 값을 지버퍼에 쓸지 확인하는 부분입니다 사용하시려면 서브서페이스처럼 지버퍼를 사용하는 애들에 묶어주면됩니다

#if MyCustom_CUSTOM_SHADER

 

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_MYCUSTOM_DEFAULT || Mat.MATERIAL_SHADINGMODEL_MYCUSTOM_FACE));

 

#else

 

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));

 

#endif

 

 

 

---------------

  • MaterialShared.h

쉐이더내부 혹은 라이팅 연산시 지버퍼 종류 검사시 등 자주 씌이는 함수로 지버퍼 확장기능을 사용하려면 이부분에 등록을 해주어야 합니다.

특정 코드 분부분에서 지버퍼에 데이터를 넣을시 선언을 같이 확인합니다 그래서 이부분을 누락시키면 지버퍼D 씌여지지 않습니다

모바일 지버퍼 확장을 쓰지 않고 있지만 밑작업으로 진행해 주었습니다.

 

 

inline bool IsSubsurfaceShadingModel(FMaterialShadingModelField ShadingModel)

{

#if MyCustom_CUSTOM_SHADER

    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_MYCUSTOM_Default) || ShadingModel.HasShadingModel(MSM_MYCUSTOM_Face) || ShadingModel.HasShadingModel(MSM_MYCUSTOM_Smooth);

#else

 

 

쉐이더

  • 선택사항
  • SubstrateDeferredLighting.ush        

함수 하위에 쉐도우 텀에서 제거(미사용으로 되어있어서 컴파일 에러납니다)

실제로 3개를 쓰며 서브스트레이트를 제거하지 않았으면 불필요한 과정입니다

// Analytical lighting evaluation for Substrate material.

// Unpack BSDF on-the-fly

FSubstrateDeferredLighting SubstrateDeferredLighting(

 

FShadowTerms ShadowTerms = { SubstrateGetAO(SubstratePixelHeader), 1.0, 1.0, InitHairTransmittanceData() };

if (SubstrateShadowTermInputParameters.bEvaluateShadowTerm)

{

// Get the shadow term that is independent from Substrate closures.

 

GetShadowTermsBase(

SubstrateShadowTermInputParameters.SceneDepth,

#if MyCustom_CUSTOM_SHADER_EDIT                

1,

#endif        

 

 

쉐이더 - 쉐이딩

쉐이딩 코드자체는 공개할 수가 없기에 개략적인 부분만 적습니다.

 

 

쉐이더 수정을 위한 사전지식

 

머터리얼 핀에 꼽힌 내용은 베이스 패스 넘어가는 시점에 지버퍼에 저장이 됩니다.

오브젝트당  지버퍼에 컬러 노멀 orm 을 기본으로 추가 정보를  적는일(베이스 패스)이 끝나면 완성된 지버퍼 텍스처로 라이팅 연산을 하며 라이팅에서 쉐이딩과 csm 쉐도우 텍스쳐를 합성해서 최종 쉐이딩을 만들게 됩니다.

컬러 관련 연산을 하려면 지버퍼에 적는 단계에서 수정하고 쉐이딩 관련 연산을 하려면 라이팅쪽에서 수정을 하면 됩니다.

 

추가적으로 포스트 단계에서 지버퍼 사용은 모바일gl은 라이팅이 포함되어 있는 컬러(포스트 인풋0)만 가능하고 그외의 플랫폼은 플랫폼에따라 몇가지 버퍼가 넘어옵니다

 

필요시 넘기는 정보를 추가는 가능하나 gl과 통일성을 생각하면 기본 지버퍼 안에서 해결하는것이 좋습니다.

 

 

void MobileFetchGBuffer(in float2 UV, out half4 GBufferA, out half4 GBufferB, out half4 GBufferC, out half4 GBufferD, out float SceneDepth)

{

함수를 보면 지버퍼를 세팅하는 기능이 존재하는데

 

이렇듯 불칸과 메탈 등에는 모바일 지버퍼 확장이 가능하게 되어있으나

 

Gl 비트 제한때문에 확장이 불가능하게 되어 있습니다.

실제로 저걸 무시하고(무시할순 없고 컴파일 에러나는 부분을 수정하면) 지버퍼를 확장해서 사용하게되면

사용은 가능하나 특정 기기들에서 크러시를 발생하게 됩니다.

 

 

쉐이더 파일 수정

 

위에 정보를 토대로

머터리얼에서 표현하고자 하는 바를 먼저 설정하면 좋습니다.

쉐이딩모델을 추가하는것은 보통 툰스타일을 표현하고싶을때가 많기에 기본적으로 툰을 표현할때 라고 생각하고 진행합니다.

 

지버퍼 구조를 보게되면

 

GbufferA rgb - WorldNorma a-objectdata

GbufferB rgb - Metal / Spec / Rough a-shadingmodel

GbufferC rgb - basecolor a-ao

GbufferD = 지버퍼 확장

 

이렇게 d 쓰지 않게되면 있는 공간이 없습니다.

 

결국 Gl 제외한 플랫폼에서는 D 사용해 여유있게 필요 정보를 지버퍼에적고 라이팅에 넘겨서 쉐이딩을 하지만 GL 동일한 이미지로 표현하기 위해서는 최소 1 에서 2개의 공간을 확보해야합니다.

 

쓰지 않는 버퍼를 비워도 되고 합쳐도 되는 버퍼를 합쳐서 사용해도됩니다.

 

저는 러프니스와 스펙큘러 메탈 세개의 채널중 스펙큘러채널에 메탈 정보를 같이 적고 메탈은 비웠으며 ao또한 비워서 사용했습니다.

 

쉐이딩모델 선언 기본 설정

 

 

ShadingCommon.ush

 

맨위에서 쉐이딩 모델의 비트를 설정합니다 여기서 알수 있는게 기타 쉐이딩 모델의 부가 옵션을 위해 5, 6, 7, 8 비트는 비워둔다는것입니다.

 

// 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

 

#if MY_CUSTOM_SHADER_EDIT

#define SHADINGMODELID_MYCUSTOM_DEFAULT     12

#define SHADINGMODELID_MYCUSTOM_FACE            13

#define SHADINGMODELID_MYCUSTOM_SMOOTH      14      // Temporary while we convert everything to Strata

#define SHADINGMODELID_NUM                  15

#else

#define SHADINGMODELID_SUBSTRATE            12      // Temporary while we convert everything to Strata

#define SHADINGMODELID_NUM                  13

#endif

#define SHADINGMODELID_MASK                 0xF     // 4 bits reserved for ShadingModelID          

// The flags are defined so that 0 value has no effect!

// These occupy the 4 high bits in the same channel as the SHADINGMODELID_*

#define HAS_ANISOTROPY_MASK             (1 << 4)

#define SKIP_PRECSHADOW_MASK            (1 << 5)

#define ZERO_PRECSHADOW_MASK            (1 << 6)

#define SKIP_VELOCITY_MASK              (1 << 7)

 

 

아래에

 

/ for debugging and to visualize

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);

#if MY_CUSTOM_SHADER_EDIT

#else  

    else if (ShadingModelID == SHADINGMODELID_SUBSTRATE) return float3(1.0f, 1.0f, 0.0f);

#endif

    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);

#if MY_CUSTOM_SHADER_EDIT

#else

        case SHADINGMODELID_SUBSTRATE:  return float3(1.0f, 1.0f, 0.0f);

#endif

        default: return float3(1.0f, 1.0f, 1.0f); // White

    }

#endif

}

쉐이딩 모델의 색상과 디버그 색상을 설정할 있습니다.

불필요하면 해주지 않아도 상관 없습니다.

 

이제 기본적인 선언 끝이났고 실제 쉐이딩을 수정할 차례입니다.

아까 말했듯이 라이팅에 사용할 정보를 베이스 패스에서 지버퍼에 적어야 합니다.

 

지버퍼에 적는 과정

지버퍼 처리 슬롯 정리

저의는 커스텀 데이터를 사용하지 않아 수정하지 않앗지만 보통 사용해서간단히 예를 작성하도록 하겠습니다.

임시로 만들어쓰는 예제라 코드자체를 복붙하게되면 실행이 되지 않습니다 ㅠㅠ

5.4 버전의 코드는 공개 할수 없어서 제가 예전에 만들엇던 코드를 확인해가며 이부분은 기록하느라 현재버전과는 조금 다를 있습니다.

 

 

머터리얼에 활성화

 

Material.cpp

 

void UMaterial::Serialize(FArchive& Ar)

{

함수 하단에 머터리얼에섯 사용하는 핀의 최대갯수를 확인하는 부분이 있습니다.

 

//test_toon 핀확장

    //static_assert(MP_MAX == 35, "New material properties must have DoMaterialAttributeReorder called on them to ensure that any future reordering of property pins is correctly applied.");

    static_assert(MP_MAX == 33, "New material properties must have DoMaterialAttributeReorder called on them to ensure that any future reordering of property pins is correctly applied.");

 

 

  • MaterialAttributeDefinitionMap.cpp

아래 함수안에 핀의 이름을 설정할 있습니다.

FText FMaterialAttributeDefinitionMap::GetAttributeOverrideForMaterial(const FGuid& AttributeID, UMaterial* Material)

{

TArray<TKeyValuePair<EMaterialShadingModel, FString>> CustomPinNames;

EMaterialProperty Property = GMaterialPropertyAttributesMap.Find(AttributeID)->Property;

 

static const IConsoleVariable* SubstrateCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.substrate"));

const bool bSubstrateEnabled = (SubstrateCVar && SubstrateCVar->GetInt() != 0);

 

switch (Property)

{

case MP_EmissiveColor:

return Material->IsUIMaterial() ? LOCTEXT("UIOutputColor", "Final Color") : LOCTEXT("EmissiveColor", "Emissive Color");

case MP_Opacity:

return bSubstrateEnabled ? LOCTEXT("OpacityOverride", "Opacity Override") : LOCTEXT("Opacity", "Opacity");

case MP_OpacityMask:

return LOCTEXT("OpacityMask", "Opacity Mask");

 

안에서

case MP_CustomData0:

CustomPinNames.Add({ MSM_ClearCoat, LOCTEXT("ClearCoat", "Clear Coat").ToString() });

CustomPinNames.Add({ MSM_Hair, LOCTEXT("Backlit", "Backlit").ToString() });

CustomPinNames.Add({ MSM_Cloth, LOCTEXT("Cloth", "Cloth").ToString() });

이런식으로 커스텀피의 이름도 설정할 있고요 클리어 코트에서는 커스텀 0번의 이름이 ClearCoat이고

Hair 에서는 Backlit 인걸 있죠

 

   //testtoon addpin

    /*case MP_CharacterSpecular:

        return LOCTEXT("CharacterSpecular", "Character Specular");*/

이런식으로 아예 커스텀 핀을 추가하거나

case MP_Refraction:

        //핀변경 FromString(GetPinNameFromShadingModelField( 라인은 커스텀핀이름을 추가해줫을때 실행해야하는듯?

        CustomPinNames.Add({ MSM_TestToon_Face , "Sensitive" });

        CustomPinNames.Add({ MSM_TestToon , "Sensitive" });

        return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, "Subsurface Color"));

        return LOCTEXT("Refraction", "Refraction");

    case MP_CustomizedUVs0:

        return LOCTEXT("CustomizedUV0", "Customized UV 0");

    case MP_CustomizedUVs1:

        return LOCTEXT("CustomizedUV1", "Customized UV 1");

 

이런식으로 기존핀의 이름을 변경해서 사용할 수도 있습니다.

 

새로운 핀을 추가하는건 인터넷에 많이 나와잇으니 생략하도록 하겠습니다.

 

업데이트되어서 안나오는부분이 하나 있는데

void FMaterialAttributeDefinitionMap::InitializeAttributeMap()

안에 핀의 attributes 설정해주어야 합니다.

//testtoon addpin

    //Add(FGuid(0xBE76F62D, 0x001F4D6C, 0x87E871C9, 0x425BCE27), TEXT("CharacterSpecular"), MP_CharacterSpecular, MCT_Float3, FVector4(0, 0, 0, 0), SF_Pixel);

 

이렇게요

 

함수는 같은 클래스의 생성자에서 호출됩니다

쉽게 말하면 init 함수랑 같은기능이므로 누락하면 안됩니다.

 

FMaterialAttributeDefinitionMap::FMaterialAttributeDefinitionMap()

    : AttributeDDCString(TEXT(""))

    , bIsInitialized(false)

{

    AttributeMap.Empty(MP_MAX);

    InitializeAttributeMap();<<호출

}

 

 

 

SceneTypes.h

위에 엔진타잎에 쉐이더 타잎을 늘려준것처럼 씬타잎에 핀을 추가해줍니다.

씬타잎은 렌더링에서 사용하는 씬프록시의 최상위 헤더라고 생각하시면 대충 맞습니다.

 

 

UENUM(BlueprintType)

enum EMaterialProperty

{

    MP_EmissiveColor = 0 UMETA(DisplayName = "Emissive"),

    MP_Opacity UMETA(DisplayName = "Opacity"),

..

..

..

 

    MP_SubsurfaceColor UMETA(DisplayName = "Subsurface"),

    //test toon addpin

    //MP_CharacterSpecular UMETA(DisplayName = "CharacterSpec"),

 

  • MaterialExpressions.cpp

 

int32 UMaterialExpressionMakeMaterialAttributes::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)

{

int32 Ret = INDEX_NONE;

UMaterialExpression* Expression = nullptr;

 

내부에

//testtoon pinadd

    //case MP_CharacterSpecular: Ret = CharacterSpecular.Compile(Compiler); Expression = CharacterSpecular.Expression; break;

 

컴파일할때 포함하라고 컴파일러에게 알려주는 과정입니다. 이런 핀이 있다고 알려주는거죠

 

void UMaterialExpressionBreakMaterialAttributes::BuildPropertyToIOIndexMap()

{

함수에 프로퍼티 설정하는데도 핀을 추가해 줍니다.(5.4버전에는 기존께 26번까지 있으니 27번으로 추가해야합니다)

PropertyToIOIndexMap.Add(MP_CustomizedUVs4,                        20);

PropertyToIOIndexMap.Add(MP_CustomizedUVs5,                        21);

PropertyToIOIndexMap.Add(MP_CustomizedUVs6,                        22);

PropertyToIOIndexMap.Add(MP_CustomizedUVs7,                        23);

PropertyToIOIndexMap.Add(MP_PixelDepthOffset,                24);

 

////testtoon addpin

 //PropertyToIOIndexMap.Add(MP_CharacterSpecular, 25);

 

 

MaterialExpressionMakeMaterialAttributes.h

 

마지막으로 머터리얼의 인터페이스에도 추가해줘야 합니다.

UCLASS(collapsecategories, hidecategories = Object, MinimalAPI)

class UMaterialExpressionMakeMaterialAttributes : public UMaterialExpression

{

GENERATED_UCLASS_BODY()

 

UPROPERTY()

FExpressionInput BaseColor;

 

..

..

..

////test toon addpin

    //UPROPERTY()

    //FExpressionInput CharacterSpecular;

=====================

이런식으로 핀을 추가 혹은 기존 슬롯에 필요한 데이터를 넣은뒤 데이터로 셀쉐이딩 처리를 하면 됩니다.

 

 

 

들어간 데이터를 지버퍼에 쓰기

  • BasePassPixelShader.usf

 

// is called in MainPS() from PixelShaderOutputCommon.usf

void FPixelShaderInOut_MainPS(

    FVertexFactoryInterpolantsVSToPS Interpolants,

    FBasePassInterpolantsVSToPS BasePassInterpolants,

    in FPixelShaderIn In,

    inout FPixelShaderOut Out)

{

 안에 커스텀 데이터에서 내부 변수에 값을 저장합니다.

기본적으로 지버퍼 확장을 쓰는경우에는 커스텀 핀을 4개를 추가하기보다는 서브서페이스 컬러 핀을 활성화해서 3채널(일반적으로 컬러)데이터를 보내고

1개정도 추가하거나 기존에 있는 핀중에 안쓰는 핀을 활성화 시켜서 사용합니다.

 

if MATERIAL_SHADINGMODEL_CLOTH || MATERIAL_SHADINGMODEL_TEST_TOON || MATERIAL_SHADINGMODEL_TEST_TOON_FACE

        else if (ShadingModel == SHADINGMODELID_CLOTH || ShadingModel == SHADINGMODELID_TEST_TOON || ShadingModel == SHADINGMODELID_TEST_TOON_FACE)

        {

머터리얼 노드의 핀의 값을 가져옵니다

float4 SubsurfaceData = GetMaterialSubsurfaceData(PixelMaterialInputs);

        }

#endif

..

..

else if (ShadingModel == SHADINGMODELID_CLOTH || ShadingModel == SHADINGMODELID_TEST_TOON || ShadingModel == SHADINGMODELID_TEST_TOON_FACE)

        {

위에서 가져온 값을 변수에 저장해줍니다

            SubsurfaceColor = SubsurfaceData.rgb;

        }

지금예에서는 리프랙션 핀의 값을 받아오기때문에 머터리얼 노드 핀의 값을 지버퍼에 저장해 줍니다.

 

#if MATERIAL_SHADINGMODEL_TEST_TOON || MATERIAL_SHADINGMODEL_TEST_TOON_FACE

    GBuffer.CustomData.z = GetMaterialRefraction(PixelMaterialInputs).x;

 

 

 

아래로 내려오면

 

SetGBufferForShadingModel(

        GBuffer,

        MaterialParameters,

        Opacity,

        BaseColor,

        Metallic,

        Specular,

        Roughness,

        Anisotropy,

        SubsurfaceColor,

        SubsurfaceProfile,

        Dither,

        ShadingModel

        );

 

함수가 존재하는데 기존세팅한 값으로 지버퍼를 새로 설정하게됩니다.

그래서 안쪽에서 커스텀 데이터 값을 연결하는 작업을 하고 그뒤에 필요한 계산(베이스컬러에서 메탈린 연산을 제거하는등) 함수 뒤에서 다시 해줘야 합니다.

 

함수 뒤에 추가해줍니다

 

//If Toon model, use base as diffuse

#if MATERIAL_SHADINGMODEL_TEST_TOON || MATERIAL_SHADINGMODEL_TEST_TOON_FACE

    GBuffer.DiffuseColor = BaseColor;

#else

    GBuffer.DiffuseColor = BaseColor - BaseColor * Metallic;

#endif

 

다시 위에서 설정하는 함수로 들어가 보면

아래로 쉐이딩 모델별 커스텀 데이터 설정이 존재합니다.

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)

{

..

..

..

// 필요한 음영 모델에 대한 사용자 정의 데이터를 계산하고 설정합니다.

    if (false)

    {

    }

#if MATERIAL_SHADINGMODEL_SUBSURFACE

    else if (ShadingModel == SHADINGMODELID_SUBSURFACE)

    {

        GBuffer.CustomData.rgb = EncodeSubsurfaceColor(SubsurfaceColor);

        GBuffer.CustomData.a = Opacity;

    }

 

이런식으로요

저의것도 하나 추가해줍니다.

 

#if MATERIAL_SHADINGMODEL_TEST_TOON || MATERIAL_SHADINGMODEL_TEST_TOON_FACE

    else if (ShadingModel == SHADINGMODELID_TEST_TOON || ShadingModel == SHADINGMODELID_TEST_TOON_FACE)

    {

        //마테리얼의 커스텀데이터 0 / 1 핀의 값을 지버퍼에 적용

        GBuffer.CustomData.xy = float2(GetMaterialCustomData0(MaterialParameters), GetMaterialCustomData1(MaterialParameters));

 

 

 

 

Ao 순수 ao 변경해주는게 좋을것 같네요 툰이니까

 

if (GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON || GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON_FACE)

    {

        GBuffer.GBufferAO = MaterialAO;

    }

else

    {

        // FIXME: ALLOW_STATIC_LIGHTING == 0 expects this to be AO

        GBuffer.GBufferAO = AOMultiBounce( Luminance( GBuffer.SpecularColor ), SpecOcclusion ).g;

    }

 

-deferredshadingCommon.ush

 

이파일은 지버퍼 관련 세팅이 있는 파일이며 커스텀 데이터를 서브서페이스 핀을 사용하면 아래 설정처럼 이게 서브서페이스모델이라 라고 알려주는 함수에

커스텀 쉐이더를 추가해야합니다.

 

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_TEST_TOON

        || ShadingModel == SHADINGMODELID_TEST_TOON_FACE;

}

bool UseSubsurfaceProfile(int ShadingModel)

{

    return ShadingModel == SHADINGMODELID_SUBSURFACE_PROFILE || ShadingModel == SHADINGMODELID_EYE;

}

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_TEST_TOON

        || ShadingModelID == SHADINGMODELID_TEST_TOON_FACE;

}

 

 

지버퍼 인코딩 / 디코딩에도 추가 각종 패스를 넘어갈때 지버퍼에 데이터를 쓰고 다음패스로 넘깁니다.

인코딩

void EncodeGBuffer(

    FGBufferData GBuffer,

    out float4 OutGBufferA,

    out float4 OutGBufferB,

    out float4 OutGBufferC,

    out float4 OutGBufferD,

    out float4 OutGBufferE,

    out float4 OutGBufferVelocity,

    float QuantizationBias = 0      // -0.5 to 0.5 random float. Used to bias quantization.

    )

함수안에

if (GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON || GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON_FACE)

        {

            OutGBufferB.rgb = GBuffer.CharacterSpecular;

            OutGBufferC.a = GBuffer.GBufferAO;

        }

        else

        {

            OutGBufferB.r = GBuffer.Metallic;

            OutGBufferB.g = GBuffer.Specular;

            OutGBufferB.b = GBuffer.Roughness;

 

디코딩

FGBufferData DecodeGBufferData(

    float4 InGBufferA,

    float4 InGBufferB,

    float4 InGBufferC,

    float4 InGBufferD,

    float4 InGBufferE,

    float4 InGBufferF,

    float4 InGBufferVelocity,

    float CustomNativeDepth,

    uint CustomStencil,

    float SceneDepth,

    bool bGetNormalizedNormal,

    bool bChecker)

{

    FGBufferData GBuffer;

GBuffer.BaseColor = DecodeBaseColor(InGBufferC.rgb);

    if (GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON || GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON_FACE)

    {

        GBuffer.Metallic = 0;

        GBuffer.Specular = 0;

        GBuffer.Roughness = 0;

        GBuffer.CharacterSpecular = InGBufferB.rgb;

        GBuffer.GBufferAO = InGBufferC.a;

        GBuffer.IndirectIrradiance = 1;

    }

    else

    {

        GBuffer.Metallic    = InGBufferB.r;

        GBuffer.Specular    = InGBufferB.g;

        GBuffer.Roughness   = InGBufferB.b;

        GBuffer.CharacterSpecular = 0;

=================================

 

이렇게 저의가 지버퍼를 사용하려고한 라이트 패스까지 안전하게 정보를 넘길 있습니다.

 

 

쉐이딩 수정과정

지버퍼에 적었다면 이제 정보로 쉐이딩 쉐도우 처리를 해야 합니다.

쉐이딩

언리얼은 brdf 모델을 사용하고 있고 harflambert 라이팅이 기본입니다

 

ShadingModels.ush

 

가장 아래로 먼저 내려갑니다

 

FDirectLighting IntegrateBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, FShadowTerms Shadow )

#endif

{

    switch( GBuffer.ShadingModelID )

    {

        case SHADINGMODELID_DEFAULT_LIT:

        case SHADINGMODELID_SINGLELAYERWATER:

        case SHADINGMODELID_THIN_TRANSLUCENT:

            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 );

#if MY_CUSTOM_SHADER_EDIT      

        case SHADINGMODELID_MYCUSTOM_DEFAULT:

            return CustomLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );

        case SHADINGMODELID_MYCUSTOM_FACE:

            return CustomFaceLitBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );

        case SHADINGMODELID_MYCUSTOM_SMOOTH:

            return CustomLitSmoothBxDF( GBuffer, N, V, L, Falloff, NoL, AreaLight, Shadow );

#endif          

        default:

            return (FDirectLighting)0;

    }

}

 

이쪽에서 쉐이딩 모델의 케이스에 따라 bxdf 모델을 구분에서 라이팅 연산을 진행하게됩니다.

 

위쪽에

bxdf 함수를 제작해주면됩니다.

 

처음부터 아예 만들수도 있지만 기본적으로 디폴트 릿의 함수를 베이스로 수정하게됩니다.

)

FDirectLighting CustomLitBxDF( FGBufferData GBuffer, half3 N, half3 V, half3 L, float Falloff, half NoL, FAreaLight AreaLight, inout FShadowTerms Shadow )

{

    BxDFContext Context;

    FDirectLighting Lighting;

    float NoV, VoH, NoH;

->각진 쉐이딩을 위해 표면에 임의로 스텝을 먹여줍니다(이것만 수정해도 기본 쉐이딩부분은 끝나긴합니다)

    NoL = step(GBuffer.GBufferAO , NoL);

   

#if SUPPORTS_ANISOTROPIC_MATERIALS

    bool bHasAnisotropy = HasAnisotropy(GBuffer.SelectiveOutputMask);

#else

    bool bHasAnisotropy = false;

#endif

/* 제거 -

    BRANCH

    if (bHasAnisotropy)

    {

        half3 X = GBuffer.WorldTangent;

        half3 Y = normalize(cross(N, X));

        Init(Context, N, X, Y, V, L);

        NoV = Context.NoV;

        VoH = Context.VoH;

        NoH = Context.NoH;

    }

    else

*/

    {

#if SHADING_PATH_MOBILE

        InitMobile(Context, N, V, L, NoL);

#else

        Init(Context, N, V, L);

#endif

        NoV = Context.NoV;

        VoH = Context.VoH;

        NoH = Context.NoH;

        SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true);

    }

    Context.NoV = saturate(abs( Context.NoV ) + 1e-5);

 

    Lighting.Diffuse = Diffuse_Lambert(GBuffer.DiffuseColor);

    Lighting.Diffuse *= AreaLight.FalloffColor * (Falloff * NoL);

 

    BRANCH

    if (bHasAnisotropy)

    {

        //Lighting.Specular = GBuffer.WorldTangent * .5f + .5f;

        Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.Anisotropy, GBuffer.SpecularColor, Context, NoL, AreaLight);

    }

    else

    {

        if( IsRectLight(AreaLight) )

        {

            Lighting.Specular = RectGGXApproxLTC(GBuffer.Roughness, GBuffer.SpecularColor, N, V, AreaLight.Rect, AreaLight.Texture);

        }

        else

        {

            Lighting.Specular = AreaLight.FalloffColor * (Falloff * NoL) * SpecularGGX(GBuffer.Roughness, GBuffer.SpecularColor, Context, NoL, AreaLight);

        }

    }

    FBxDFEnergyTerms EnergyTerms = ComputeGGXSpecEnergyTerms(GBuffer.Roughness, Context.NoV, GBuffer.SpecularColor);

 

/* - 부드러운 쉐이딩 처리해주는 부분이라 제거

    // Add energy presevation (i.e. attenuation of the specular layer onto the diffuse component

    Lighting.Diffuse *= ComputeEnergyPreservation(EnergyTerms);

    // Add specular microfacet multiple scattering term (energy-conservation)

    Lighting.Specular *= ComputeEnergyConservation(EnergyTerms);

*/

    Lighting.Transmission = 0;

    return Lighting;

}

 

위의 예시처럼 기본릿 쉐이딩을 기본으로 수정해주면됩니다 그럼

소스의 맨아래서 케이스로 처리한것을 따라 커스텀으로 추가한 모델은 다른 라이팅 연산을 하게되는것입니다.

 

 

그림자 합성

 

그림자는 이곳이 아닌

  • DeferredLightingCommon.ush

에서 진행하게 됩니다.

여기서는 수정하지 않지만

void MobileDirectionalLightPS(

usf 파일에 있는 메인라이팅 연산 함수에서

 

호출하는 함수가

 

FLightAccumulator AccumulateDynamicLighting(

함이며 함수를 수정해서 최종 라이팅연산을 마무리하게 됩니다.

우리가 수정해야할 부분은 함수의 마지막라인에

LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, MaskedLightColor * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );

있는 라인이며

쉐도우 합성을 어디에서 하던 결론은 함수에 들어오는 Shadow.SurfaceShadow 변수를 셀화를 해야합니다.

 

변수를 셀화를 하는것은 정해진 곳이 있다기보다는 많은 방법이 있습니다.

아래 예시처럼 넘어온 정보를 셀화를 수도 있고

 

아예 처음부터 쉐도우를 수집할

void GetShadowTermsBase( 함수에서

OutShadow.TransmissionShadow = min(lerp(LightAttenuation.y, StaticShadowing, DynamicShadowFraction), LightAttenuation.w);

 

OutShadow.SurfaceShadow *= LightAttenuation.z;

OutShadow.TransmissionShadow *= LightAttenuation.z;

 

이부분의 outshadow 부분을 수정해주어도 됩니다.

 

 

아래의 예는 넘어온 쉐도우 정보를 단순 셀화를 하는 방식으로 공개 가능한 개인 테스트용 코드입니다.

 

//Change accumulator calculation based on model, particularly Toon-type models

            if ( GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON || GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON_FACE)

            {

                float SurfaceShadowBias = 0.3f;

                float NoL = dot(N, L);

                float AO = GBuffer.GBufferAO;

                float DiffuseBoost = GBuffer.CustomData.w; // 밝은 영역(음영이 없는 부분)을 부스트

                float3 SubsurfaceColor = ExtractSubsurfaceColor(GBuffer);

                BRANCH

               

                if (GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON)

                {

                   

                    float3 SoftLightDiffuse;

                    SoftLightBlend_float3(Lighting.Diffuse, AO.xxx, 0.05f, SoftLightDiffuse);

                    float3 SSSShadow = lerp(saturate(SoftLightDiffuse) * 0.98f * SubsurfaceColor, SubsurfaceColor, AO);

                    float AOWeightedNoL = min(saturate(Shadow.SurfaceShadow + SurfaceShadowBias), saturate(NoL*0.5 + 0.5)) * AO;

                    Attenuation = lerp( SSSShadow, DiffuseBoost, smoothstep(0.5, 0.52f, AOWeightedNoL) );

               

                }

 

                else if (GBuffer.ShadingModelID == SHADINGMODELID_TEST_TOON_FACE)

                {

                   

                    float3 SSSShadow = SubsurfaceColor;

                    //차폐 그림자와 얼굴 텍스쳐 그림자 smoothstep 수치 분리

                    float AOWeightedNoL = min(smoothstep(0.3, 0.32, saturate(Shadow.SurfaceShadow)), (smoothstep(0.5, 0.52,  AO)));

                    Attenuation = lerp( SSSShadow, DiffuseBoost, smoothstep(0.5, 0.52f, AOWeightedNoL));    

                }

 

 

                if (LightData.bRadialLight)

                {

                    // unlit behind the light

                    Attenuation = Attenuation * step(0.0, NoL);

                }

                LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Attenuation * 0.4, bNeedsSeparateSubsurfaceLightAccumulation );

            }

            else

            {

                LightAccumulator_AddSplit( LightAccumulator, Lighting.Diffuse, Lighting.Specular, Lighting.Diffuse, LightColor * LightMask * Shadow.SurfaceShadow, bNeedsSeparateSubsurfaceLightAccumulation );

            }

            LightAccumulator_AddSplit( LightAccumulator, Lighting.Transmission, 0.0f, Lighting.Transmission, LightColor * LightMask * Shadow.TransmissionShadow, bNeedsSeparateSubsurfaceLightAccumulation );

 

'unreal > UnrealRendering' 카테고리의 다른 글

[Unreal_Rendering]HeightFog 개선  (0) 2024.06.12
[Unreal_Rendering]ToneMapper 수정  (0) 2024.06.12