본문 바로가기

Engine Development

[번역] UE5 Toon Shading 에서 Color Curve를 적용하는 방법

728x90
반응형

 

LINEAR COLOR CURVE ATLAS

  • 셀쉐이딩 방법의 경우 선형 색상 곡선 아틀라스를 사용하여 모든 값을 사용합니다.
  • 아틀라스는 여러 LUT 곡선을 하나의 텍스처에 저장하고 셰이더에서 샘플링하여 곡선 값을 다시 가져옵니다.
  • 머트리얼에서 선택할 수 있는 여러 곡선을 가질 수 있습니다. 

  • Matt Oztalay의 GDC Talk에서 배운 내용은 작은 곡선 텍스처에서는 비용이 순수(계산되는) 수학에 비해 그렇게 크지 않다는 것입니다. 
  • 물론 사용되는 수학과 하드웨어에 따라 다릅니다. 이를 순수하고 정확한 값으로 받아들이지 마십시오.
  • 항상 프로파일링은 가장 친한 친구입니다.

  • 순수(계산되는) 수학에 비해 아티스트/디자이너는 빛이 어떻게 반응할지 절대적으로 제어할 수 있습니다. 
  • 정말 펑키(?)한 빛 반응을 만들 수 있는 가능성을 제공합니다.

 

 

Creating the Linear Color Curve Atlas

  • 엔진 콘텐츠에 폴더를 추가해야 합니다. 향후 모든 프로젝트에서 공유되는 기본 리소스가 될 것입니다. 
  • 나중에 사용할 아틀라스를 변경할 수 있는 옵션을 추가했습니다.
  • Engine/Content 폴더 로 이동하여 "Celshading" 폴더를 추가하세요 .

폴더 위치

 

편집기 내부의 새로 생성된 폴더에서 선형 색상 곡선 아틀라스 와 일부  선형 색상 곡선을 만듭니다 .

 

  • Engine/Source/Runtime/Engine/Classes/Engine/  폴더에 CelshadingSettings.h 라는 새 C++ 헤더(.h)를 생성합니다 .
  • 이 파일 안에는 UDevelopperSettings 에서 파생된 UCelshadingSettings 클래스가 포함되어 있습니다 .
  • 모든 UDevelopperSettings  클래스는 프로젝트 설정 메뉴 내에 자동으로 등록됩니다. 

 

// Copyright YourCopyright. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Engine/DeveloperSettings.h"

#include "CelshadingSettings.generated.h"

class UCurveLinearColorAtlas;

/**
 * Settings for celshading properties. This comment is displayed as a description in the project settings menu.
 */
UCLASS(config = Engine, defaultconfig, meta = (DisplayName = "Celshading"))
class ENGINE_API UCelshadingSettings : public UDeveloperSettings
{
	GENERATED_UCLASS_BODY()

public:
	UPROPERTY(config, EditAnywhere, Category = General, meta = (DisplayName = "Celshading curve atlas",
		ToolTip = "Linear color curve atlas used for Celshading calculation.",
		ConfigRestartRequired = false))
	TSoftObjectPtr<UCurveLinearColorAtlas> CelshadingCurveAtlas;

#if WITH_EDITOR
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
};

 

  • UCLASS를  엔진  구성 으로 정의하므로 변경 사항은 DefaultEngine.ini 내부에 저장됩니다. 
    • 여기서는 defaultconfig로 설정합니다  . 
  • 우리의 변경 사항이 모든 프로젝트에 걸쳐 전역적으로 적용된다는 것을 의미합니다. 
  • 각 프로젝트마다 하나의 아틀라스가 필요한 경우 이 태그를 제거하세요. 
    • DisplayName  은 프로젝트 설정 내의 메뉴 이름입니다 .
  • 그런 다음 멤버 TSoftObjectPtr을 정의합니다 . 
  • 소프트 참조를 사용하므로 메뉴를 열 때 자산을 로드하지 않습니다(필요하지 않음). 
  • UPROPERTY를 .ini에 저장해야 함을 알리기 위해  "config" 태그를 사용합니다 . 
  • 마지막에는 편집기에서만 PostEditChangeProperty () 함수를 덮어씁니다.
  • 이 함수는 편집기에서 UPROPERTY가  변경될 때마다 트리거됩니다.
  • 이제 Engine/Source/Runtime/Engine/Private/ 폴더 안에 CelshadingSettings.cpp를 생성합니다 . 
// Copyright YourCopyright. All Rights Reserved.

#include "Engine/CelshadingSettings.h"
#include "Curves/CurveLinearColorAtlas.h"

UCelshadingSettings::UCelshadingSettings(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{

}

void UCelshadingSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
	Super::PostEditChangeProperty(PropertyChangedEvent);
	// If a property change
	if (PropertyChangedEvent.Property)
	{
		// If it's our celshading atlas
		if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UCelshadingSettings, CelshadingCurveAtlas))
		{
			// Try to load the Celshading atlas and check if it's not nullptr
			UCurveLinearColorAtlas* ObjectAtlas = CelshadingCurveAtlas.LoadSynchronous();
			if (ObjectAtlas)
			{
				// The logic after loading the new atlas
			}
		}
	}
}
  • 여기에는 클래스에 대한 기본 생성자가 있습니다(어쨌든 필요하지만 그렇지 않으면 컴파일되지 않습니다).
  • PostEditChangeProperty()  함수도 있습니다. 
  • 셀쉐이딩 하위 시스템을 생성해야 하므로 로직이 비어 있습니다. 
  • 마지막으로 Config/BaseEngine.ini  내부에  다음 줄을 추가합니다
    • [/Script/Engine.CelshadingSettings] CelshadingCurveAtlas=/Engine/CelShading/CurveAtlas_DefaultCelShading.CurveAtlas_DefaultCelShading
  • Unreal은 구성 파일에 Variable=Value  형식을 사용하므로  여기서는 CelshadingCurveAtlas=PathToYourAtlas라고 합니다.
  • 셀쉐이딩을 마우스 오른쪽 버튼으로 클릭하고 " 참조 복사" 를 선택하여 셀쉐이딩의 상대 경로를 얻을 수 있습니다 .

 

Celshading Engine Subsystem

  • Celshading 아틀라스에 대한 참조를 로드하고 저장해야 합니다. 
  • 우리는 엔진 하위 시스템을 사용하고 있습니다 . 
  • 하위 시스템은 고전적인 C++ 싱글톤처럼 작동합니다. 
  • 그러나 장점은 수명이 엔진 자체에 의해 자동으로 관리된다는 것입니다. 
  • 다양한 유형의 하위 시스템이 있으며 변경은 대부분 생성 및 소멸될 때 발생합니다. 
    • 이에 대한 자세한 내용은 위키를 확인하세요.
  • 우리는 엔진 하위 시스템을 사용하고 있으므로 엔진(및 편집기) 시작 시 생성되고 엔진 수명이 끝나면 제거됩니다.

  • Engine/Source/Runtime/Engine/Public/Subsystems/  폴더 안에 헤더를 생성합니다. 
  • 이름을 CelshadingSubsystem.h 로 지정합니다 .

 

// Copyright YourCopyright. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/EngineSubsystem.h"
#include "CelshadingSubsystem.generated.h"

class UCurveLinearColorAtlas;

/**
 * UCelshadingSubsystem
 * Celshading subsystem to load and manage curve atlas.
 */
UCLASS()
class ENGINE_API UCelshadingSubsystem : public UEngineSubsystem
{
	GENERATED_BODY()

public:
	// Initialize the subsystem, we load the curve atlas here
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;

	// Triggered at the end of the subsystem lifetime, for cleanup
	virtual void Deinitialize() override;

	// Getter for the celshading curve atlas
	TObjectPtr<UCurveLinearColorAtlas> GetCelshadingCurveAtlas() const;

	// Update the reference of the celshading curve atlas
	void UpdateCelshadingCurveAtlas(UCurveLinearColorAtlas* InAtlas);

private:
	// The reference to the Celshading linear color curve atlas
	UPROPERTY()
	TObjectPtr<UCurveLinearColorAtlas> CelshadingCurveAtlas;
};

 

  • 참고:  하위 시스템에서는 원시 UPROPERTY 포인터를 생성할 수 없습니다 . TObjectPtr<> 이어야 합니다 .
    • 더 이상 설명이 필요하지 않다고 생각합니다. 대부분 고전적인 수업입니다.
  • C++ 파일을 살펴보겠습니다.
  • Engine/Source/Runtime/Engine/Private/Subsystems/  내부에 생성 하고 이름을 CelshadingSubsystem.cpp 로 지정합니다 .  
// Copyright YourCopyright. All Rights Reserved.

#include "Subsystems/CelshadingSubsystem.h"
#include "Engine/CelshadingSettings.h"
#include "Curves/CurveLinearColorAtlas.h"

void UCelshadingSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	
	// We get our default celshading settings class
	const UCelshadingSettings* CelshadingSettings = GetDefault<UCelshadingSettings>();

	// We load our Celshading curve atlas
	CelshadingCurveAtlas = CelshadingSettings->CelshadingCurveAtlas.LoadSynchronous();

	// If the celshading atlas is nullptr (error in loading), display an error.
	if (!CelshadingCurveAtlas)
	{
		UE_LOG(LogInit, Error, TEXT("Can't load Celshading atlas, please check selected asset in project settings"));
	}
}

void UCelshadingSubsystem::Deinitialize()
{
	// Remove the reference count (not sure if it's useful for garbage collector)
	CelshadingCurveAtlas = nullptr;
}

void UCelshadingSubsystem::UpdateCelshadingCurveAtlas(UCurveLinearColorAtlas* InAtlas)
{
	// We could do more check here.
	CelshadingCurveAtlas = InAtlas;
}

TObjectPtr<UCurveLinearColorAtlas> UCelshadingSubsystem::GetCelshadingCurveAtlas() const
{
	return CelshadingCurveAtlas;
}
  • 이제 하위 시스템이 완성되었습니다. 
  • Celshading 설정 내에서 PostEditChangeProperty()를 수정할 수 있습니다.
  • UCelshadingSettings.cpp 파일 로 돌아가서 하위 시스템을 추가합니다.
void UCelshadingSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
	Super::PostEditChangeProperty(PropertyChangedEvent);

	if (PropertyChangedEvent.Property)
	{
		if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UCelshadingSettings, CelshadingCurveAtlas))
		{
			UCurveLinearColorAtlas* ObjectAtlas = CelshadingCurveAtlas.LoadSynchronous();
			if (ObjectAtlas)
			{
				// Our new logic
				UCelshadingSubsystem* CelshadingSubsystem = GEngine->GetEngineSubsystem<UCelshadingSubsystem>();
				if (CelshadingSubsystem)
				{
					CelshadingSubsystem->UpdateCelshadingCurveAtlas(ObjectAtlas);
				}
			}
		}
	}
}
  • 중요사항 :  여기처럼 엔진 외부에 C++ 파일을 추가하는 경우 Visual Studio 프로젝트 파일을 다시 생성해야 합니다. 
  • UnrealBuildTool이 업데이트되고 이와 관련된 .generated.h 파일이 생성됩니다. 
  • 그 이후에도 누락으로 인해 컴파일 오류가 발생하는 경우 엔진 Intermediate 폴더 내의 콘텐츠를 삭제해야 할 수도 있습니다.

 

VIEW UNIFORM SHADER STRUCT

  • 언리얼 엔진은 렌더 종속성 그래프(RDG)를 사용하여 코드 생성과 렌더링 스레드(C++ 부분)와 셰이더(HLSL) 간의 연결을 관리합니다. 
  • 필요한 모든 리소스는 매크로를 통해 자동으로 생성됩니다. 
  • 예를 들어 다음과 같은 특수 구조를 사용합니다.

 

HLSL에서는 다음과 같습니다.

 

  • 일반적인 RDG에 대해서는 이야기하지 않겠습니다. 
  • 문서나 Froyok 의 기사( TIPS & STUDIES  장의 링크)를 읽어보세요. 
  • 셰이더 구조체를 처음부터 만드는 대신 우리 목적에 딱 맞는 이미 만들어진 구조체를 사용합니다. 
  • FViewUniformShaderParameters  구조체 입니다 .
  • 이 구조체에는 렌더링을 위한 모든 공통 변수가 포함되어 있습니다. 
  • skylight parameters, hair parameters, landscape settings, etc… 등과 같이 목록이 상당히 큽니다.

  • 여기에 셀쉐이딩 아틀라스 텍스처를 추가하겠습니다. 
  • FViewUniformShaderParameters  구조체는 렌더링 스레드에 있으므로 CelshadingSettings  클래스 에서 셀셰이딩 아틀라스를 직접 로드할 수 없습니다 (어설션을 얻으세요, 저를 믿으세요...).

  • Gamethread에서 Renderthread로 리소스를 보내기 위해 Unreal은 우리가 프록시라고 부르는 것을 사용합니다. 
  • 프록시는 Gamethread에서 생성된 다음 Renderthread로 전송됩니다. 
  • 예를 들어 skylight component는 다음 프록시 클래스를 사용합니다.
/** Represents a USkyLightComponent to the rendering thread. */
class ENGINE_API FSkyLightSceneProxy
{
public:

	/** Initialization constructor. */
	FSkyLightSceneProxy(const class USkyLightComponent* InLightComponent);

	void Initialize(
		float InBlendFraction, 
		const FSHVectorRGB3* InIrradianceEnvironmentMap, 
		const FSHVectorRGB3* BlendDestinationIrradianceEnvironmentMap,
		const float* InAverageBrightness,
		const float* BlendDestinationAverageBrightness);

	const USkyLightComponent* LightComponent;
	FTexture* ProcessedTexture;
	float BlendFraction;
	float SkyDistanceThreshold;
	FTexture* BlendDestinationProcessedTexture;

// [...] There are too many members, I removed some for visibility

	bool bLowerHemisphereIsSolidColor;

	bool IsMovable() { return bMovable; }

	void SetLightColor(const FLinearColor& InColor)
	{
		LightColor = InColor;
	}
	FLinearColor GetEffectiveLightColor() const;

#if WITH_EDITOR
	float SecondsToNextIncompleteCapture;
	bool bCubemapSkyLightWaitingForCubeMapTexture;
	bool bCaptureSkyLightWaitingForShaders;
	bool bCaptureSkyLightWaitingForMeshesOrTextures;
#endif

private:
	FLinearColor LightColor;
	const uint8 bMovable : 1;
};

 

  • 이 클래스는 장면에 채광창 구성요소가 생성될 때 생성됩니다. 
  • 생성자에서는 람다 함수와 ENQUEUE_RENDER_COMMAND 매크로 명령을 통해 자체 참조를 Renderthread로 보냅니다 .
FSkyLightSceneProxy::FSkyLightSceneProxy(const USkyLightComponent* InLightComponent)
	: LightComponent(InLightComponent)
	, ProcessedTexture(InLightComponent->ProcessedSkyTexture)

// [...] There are too many arguments, I removed some for visibility

#if WITH_EDITOR
	, SecondsToNextIncompleteCapture(0.0f)
	, bCubemapSkyLightWaitingForCubeMapTexture(false)
	, bCaptureSkyLightWaitingForShaders(false)
	, bCaptureSkyLightWaitingForMeshesOrTextures(false)
#endif
	, LightColor(FLinearColor(InLightComponent->LightColor) * InLightComponent->Intensity)
	, bMovable(InLightComponent->IsMovable())
{
	const FSHVectorRGB3* InIrradianceEnvironmentMap = &InLightComponent->IrradianceEnvironmentMap;
	const FSHVectorRGB3* BlendDestinationIrradianceEnvironmentMap = &InLightComponent->BlendDestinationIrradianceEnvironmentMap;
	const float* InAverageBrightness = &InLightComponent->AverageBrightness;
	const float* BlendDestinationAverageBrightness = &InLightComponent->BlendDestinationAverageBrightness;
	float InBlendFraction = InLightComponent->BlendFraction;
	FSkyLightSceneProxy* LightSceneProxy = this;
	ENQUEUE_RENDER_COMMAND(FInitSkyProxy)(
		[InIrradianceEnvironmentMap, BlendDestinationIrradianceEnvironmentMap, InAverageBrightness, BlendDestinationAverageBrightness, InBlendFraction, LightSceneProxy](FRHICommandList& RHICmdList)
		{
			// Only access the irradiance maps on the RT, even though they belong to the USkyLightComponent, 
			// Because FScene::UpdateSkyCaptureContents does not block the RT so the writes could still be in flight
			LightSceneProxy->Initialize(InBlendFraction, InIrradianceEnvironmentMap, BlendDestinationIrradianceEnvironmentMap, InAverageBrightness, BlendDestinationAverageBrightness);
		});
}

 

  • 이 매크로 명령 ENQUEUE_RENDER_COMMAND는 액세스 위반을 일으키는 두 스레드 간의 충돌을 방지하는 데 중요합니다. 
  • 구성 요소에서 속성이 업데이트될 때마다 이 매크로를 호출하여 Renderthread에서 이를 업데이트합니다.
  • 여기서와 같이 장면 프록시를 가져오고 참조를 다시 보냅니다.
/** 
 * Fast path for updating light properties that doesn't require a re-register,
 * Which would otherwise cause the scene's static draw lists to be recreated.
*/
void USkyLightComponent::UpdateLimitedRenderingStateFast()
{
	if (SceneProxy)
	{
		FSkyLightSceneProxy* LightSceneProxy = SceneProxy;
		FLinearColor InLightColor = FLinearColor(LightColor) * Intensity;
		float InIndirectLightingIntensity = IndirectLightingIntensity;
		float InVolumetricScatteringIntensity = VolumetricScatteringIntensity;
		ENQUEUE_RENDER_COMMAND(FFastUpdateSkyLightCommand)(
			[LightSceneProxy, InLightColor, InIndirectLightingIntensity, InVolumetricScatteringIntensity](FRHICommandList& RHICmdList)
			{
				LightSceneProxy->SetLightColor(InLightColor);
				LightSceneProxy->IndirectLightingIntensity = InIndirectLightingIntensity;
				LightSceneProxy->VolumetricScatteringIntensity = InVolumetricScatteringIntensity;
			});
	}
}

 

  • 이제 당신은 이 모든 것을 배웠습니다. 우리는 그렇게 하지 않을 것입니다. :D
    • 나는 이 문제를 제대로 해결하지 못했습니다(솔직히 말해서 많이 시도하지는 않았습니다). 
  • 하지만 이 방법은 반드시 수행해야 하며, 더 나은 방법은 자신만의 셰이더 구조체를 생성하는 것입니다.

  • /!\ 고지 사항: 셀셰이딩 아틀라스를 쉽게 얻기 위해 하위 시스템에서 직접 참조를 가져올 것입니다. 
  • 이것은 스레드에 안전하지는 않지만 지금까지 이 때문에 충돌이 발생한 적은 없습니다. 
  • 대부분의 충돌은 커브를 편집할 때 커브 계산이 이상한 작업을 수행했기 때문입니다
    • (오류는 커브의 C++ 파일 내부에서 포착되었습니다). 
  • 런타임/게임에서 값을 읽기만 한다면 괜찮을 것입니다.

 

View Uniform Shader Parameters Struct

  • FViewUniformShaderParameters  구조체 에 리소스를 생성해야 합니다 . 
  • 그러면 HLSL 변수가 생성됩니다. SceneView.h 파일로 이동하여 다음을 검색합니다.

    BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT_WITH_CONSTRUCTOR(FViewUniformShaderParameters, ENGINE_API)

마지막에 3개의 새로운 매개변수를 추가합니다:

//Celshading Lucas : Add necessary ressources to view structure
SHADER_PARAMETER_TEXTURE(Texture2D, CelshadingAtlas)
SHADER_PARAMETER_SAMPLER(SamplerState, CelshadingSampler)
SHADER_PARAMETER(uint32, CelshadingTextureHeight)

 

  • 아틀라스 RHI 텍스처, 샘플러 및 텍스처 높이에 대한 전역 단위(나중에 선택할 곡선을 결정하는 데 사용됨).

  • 이제 생성자에서 값을 초기화해야 합니다. 
  • 텍스처와 값은 엔진 시작 시 전송되지 않고 Renderthread는 실제로 nullptr을 좋아하지 않기 때문에 필수입니다(엔진이 충돌할 것입니다 )  .

  • SceneManagement.cpp 
    • 파일에서 생성자 FViewUniformShaderParameters::FViewUniformShaderParameters()  로 이동하여 끝에 다음을 추가합니다.

 

FViewUniformShaderParameters::FViewUniformShaderParameters()
{
	FMemory::Memzero(*this);

	FRHITexture* BlackVolume = (GBlackVolumeTexture &&  GBlackVolumeTexture->TextureRHI) ? GBlackVolumeTexture->TextureRHI : GBlackTexture->TextureRHI;
	FRHITexture* BlackUintVolume = (GBlackUintVolumeTexture &&  GBlackUintVolumeTexture->TextureRHI) ? GBlackUintVolumeTexture->TextureRHI : GBlackTexture->TextureRHI;
	check(GBlackVolumeTexture);
[...]

	//Celshading Lucas: Initialize Celshading member of FViewUniformShaderParameters
	CelshadingTextureHeight = 32;
	CelshadingSampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
	CelshadingAtlas = GWhiteTexture->TextureRHI;
}

 

  • 템플릿을 사용하여  포인트 샘플링을 사용하는 전역 샘플러를 생성 합니다.
  • 바이리니어는 위쪽과 아래쪽의 픽셀을 혼합하므로 선이 서로 혼합되므로 포인트 샘플링을 사용해야 합니다. 
  • 0 이전과 1 이후의 값을 원하지 않기 때문에 고정합니다.
  • 마지막으로 전역 흰색 텍스처로 커브 아틀라스를 초기화합니다.
  • 이제 Celshading 아틀라스를 업데이트하려면 SceneRendering.cpp로 이동하여 FViewInfo::SetupUniformBufferParameters() 함수를 검색하고 다음 줄을 추가해야 합니다.
/** Creates the view's uniform buffers given a set of view transforms. */
void FViewInfo::SetupUniformBufferParameters(
	const FViewMatrices& InViewMatrices,
	const FViewMatrices& InPrevViewMatrices,
	FBox* OutTranslucentCascadeBoundsArray,
	int32 NumTranslucentCascades,
	FViewUniformShaderParameters& ViewUniformShaderParameters) const
{
	check(Family);

	const FSceneTexturesConfig& SceneTexturesConfig = GetSceneTexturesConfig();

	// Create the view's uniform buffer.

	/ Mobile multi-view is not side by side
	const FIntRect EffectiveViewRect = (bIsMobileMultiViewEnabled) ? FIntRect(0, 0, ViewRect.Width(), ViewRect.Height()) : ViewRect;

	// Scene render targets may not be created yet; avoids NaNs.
	FIntPoint EffectiveBufferSize = SceneTexturesConfig.Extent;
	EffectiveBufferSize.X = FMath::Max(EffectiveBufferSize.X, 1);
	EffectiveBufferSize.Y = FMath::Max(EffectiveBufferSize.Y, 1);
	
	// [...]

	//Celshading Lucas : update Celshading texture reference and its value, each frame.
	if (UCelshadingSubsystem* CelshadingSubsystem = GEngine->GetEngineSubsystem<UCelshadingSubsystem>())
	{
		if (TObjectPtr<UCurveLinearColorAtlas> CelshadingAtlas = CelshadingSubsystem->GetCelshadingCurveAtlas())
		{
			ViewUniformShaderParameters.CelshadingAtlas = CelshadingAtlas->TextureReference.TextureReferenceRHI;
			ViewUniformShaderParameters.CelshadingTextureHeight = CelshadingAtlas->TextureHeight;
		}
	}
}

 

  • 파일 상단에 곡선 아틀라스와 사용자 정의 하위 시스템 헤더를 포함하는 것을 잊지 마세요:
    • #include "Subsystems/CelshadingSubsystem.h" 
    • #include "Curves/CurveLinearColorAtlas.h"
  • SetupUniformBufferParameters() 는 프레임마다 실행되어 View 구조체의 여러 값을 업데이트하므로 아틀라스에 대한 모든 변경 사항이 즉시 업데이트됩니다.

  • 우리의 아틀라스는 셰이더에서 사용할 준비가 되었습니다. 
  • 이제 우리에게는 2가지 선택이 있습니다. 
  • 셀쉐이딩 정보를 저장하거나 기존 CustomData 를 사용하기 위해 새로운 gbuffer 렌더 타겟을 생성합니다 . 
  • 둘 다 위아래로 있습니다.

새 재질 사용자 정의 출력 노드 만들기

  • 머티리얼의 값을 GBuffer로 전달하려면 2가지 선택이 있습니다. 
  • 기존 재질 핀(CustomData0)을 사용하거나 새 사용자 정의 출력 노드를 생성합니다.

 

  • 우리는 후자를 사용할 것입니다. 왜 ? 당신은 물어볼 수 있습니다. 
  • 첫 번째 옵션(Customdata0 사용)을 선택한 후 MakeMaterialAttribute  및 BreakMaterialAttribute  노드 도 변경해야 한다는 것을 알았기 때문입니다. 
    • 그리고 지인의 말에 따르면 그렇게 하는 것은 지옥이다. 그럼 쉬운 방법으로 해보겠습니다.
  • Engine/Source/Runtime/Engine/Classes/Materials/ 폴더를 확인하면 모든 머티리얼 표현식 노드를 볼 수 있습니다.
  • 각 노드마다 하나의 파일이 있습니다.
  • C++ 헤더 파일을 만들고 이름을 MaterialExpressionCelshadingCustomOutput.h 로 지정했습니다 . 
    • 이 노드를 생성하기 위해 BendNormal 구성을 복사했습니다 . 
    • UMaterialExpressionBentNormalCustomOutput 클래스를 검색하면 찾을 수 있습니다 .

#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MaterialExpressionIO.h"
#include "Materials/MaterialExpressionCustomOutput.h"
#include "MaterialExpressionCelshadingCustomOutput.generated.h"

UCLASS()
class UMaterialExpressionCelShadingOutput : public UMaterialExpressionCustomOutput
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(meta = (RequiredInput = "true"))
	FExpressionInput Input;

#if WITH_EDITOR
	virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
	virtual void GetCaption(TArray<FString>& OutCaptions) const override;
	virtual uint32 GetInputType(int32 InputIndex) override { return MCT_Float1; }
	virtual FExpressionInput* GetInput(int32 InputIndex) override;
#endif
	virtual int32 GetNumOutputs() const override { return 1; }
	virtual FString GetFunctionName() const override { return TEXT("GetCelShadingSelection"); }
	virtual FString GetDisplayName() const override { return TEXT("CelShading curve selection"); }
};

 

  • 먼저 필요한 모든 종속성을 포함합니다. 
  • UMaterialExpressionCustomOutput 클래스의 하위 항목인 UMaterialExpressionCelShadingOutput  이라는 새 UCLASS()를 만듭니다 . 
  • UMaterialExpressionCustomOutput은  HLSL 게터를 자동으로 생성하는 놀라운 클래스입니다. 
  • 클래식 머티리얼 핀처럼 원하는 곳 어디에서나 베이스패스에서 호출할 수 있습니다.

  • 나는 메소드의 이름이 설명이 필요하다고 생각합니다. 
    • 중요한 것은 GetFunctionName() 입니다 . 
    • 이는 HLSL 내부의 함수 이름이 됩니다. 그래서 내 것은 GetCelShadingSelection0() 이 될 것입니다 . 
    • 노드에는 여러 핀이 포함될 수 있기 때문입니다. 마지막에 색인을 추가합니다.
  • 메서드의 내용을 정의해야 합니다. 
    • 모든 머티리얼 표현식 노드는 MaterialExpressions.cpp 파일에 정의되어 있습니다 . 
    • 방금 BentNormal을 복사했습니다. 
    • 파일 상단에  "Materials/MaterialExpressionCelShadingCustomOutput.h"를 #include하는 것을 잊지 마세요 .

///////////////////////////////////////////////////////////////////////////////
// Celshading Lucas: Custom material expression for celshading curve selection
///////////////////////////////////////////////////////////////////////////////

UMaterialExpressionCelShadingOutput::UMaterialExpressionCelShadingOutput(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
#if WITH_EDITORONLY_DATA
	// Structure to hold one-time initialization
	struct FConstructorStatics
	{
		FText NAME_Utility;
		FConstructorStatics(const FString& DisplayName, const FString& FunctionName)
			: NAME_Utility(LOCTEXT("Utility", "Utility"))
		{
		}
	};
	static FConstructorStatics ConstructorStatics(GetDisplayName(), GetFunctionName());

	MenuCategories.Add(ConstructorStatics.NAME_Utility);

	bCollapsed = true;

	// No outputs
	Outputs.Reset();
#endif
}


#if WITH_EDITOR
int32  UMaterialExpressionCelShadingOutput::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
	if (Input.GetTracedInput().Expression)
	{
		return Compiler->CustomOutput(this, OutputIndex, Input.Compile(Compiler));
	}
	else
	{
		return CompilerError(Compiler, TEXT("Curve selection is missing"));
	}
	return INDEX_NONE;
}


void UMaterialExpressionCelShadingOutput::GetCaption(TArray<FString>& OutCaptions) const
{
	OutCaptions.Add(FString(TEXT("Celshading")));
}

FExpressionInput* UMaterialExpressionCelShadingOutput::GetInput(int32 InputIndex)
{
	return &Input;
}
#endif // WITH_EDITOR

 

  • 머티리얼이 컴파일되면 이 함수  Compile() 을 호출합니다 .
    • 핀이 연결되어 있는지 확인하고 그렇지 않으면 오류가 발생합니다.
    • GetCaption() 함수  는 노드의 이름입니다. 
    • GetInput()은 단지 입력을 얻는 역할을  합니다 .
    • 여기에는 하나만 있으므로 입력 멤버만 반환합니다.

 
알림:  Visual Studio 프로젝트 파일을 다시 생성하는 것을 잊지 마세요.

 

Subsurface color and opacity pins

  • Celshading 하위 표면 모델의 경우 하위 표면 색상 및 불투명도 핀을 잠금 해제해야 합니다. 
  • MakeMaterialAttribute 노드 에서 사용할 수 있으므로 사용해도 괜찮습니다 .

 

 

  • Material.cpp 파일 내 IsPropertyActive_Internal() 함수에서 활성화합니다 .

 

static bool IsPropertyActive_Internal(EMaterialProperty InProperty,
	EMaterialDomain Domain,
	EBlendMode BlendMode,
	EStrataBlendMode StrataBlendMode,
	FMaterialShadingModelField ShadingModels,
	ETranslucencyLightingMode TranslucencyLightingMode,
	bool bBlendableOutputAlpha,
	bool bHasRefraction,
	bool bUsesShadingModelFromMaterialExpression,
	bool bIsTranslucencyWritingVelocity,
	bool bIsSupported)
{
	const bool bStrataEnabled = Engine_IsStrataEnabled();

// [...]

	case MP_Opacity:
		Active = (bIsTranslucentBlendMode && BlendMode != BLEND_Modulate) || ShadingModels.HasShadingModel(MSM_SingleLayerWater);
		if (IsSubsurfaceShadingModel(ShadingModels))
		{
			Active = true;
		}
		break;

// [...]

	case MP_SubsurfaceColor:
		Active = ShadingModels.HasAnyShadingModel({ MSM_Subsurface, MSM_PreintegratedSkin, MSM_TwoSidedFoliage, MSM_Cloth, MSM_CS_Subsurface, MSM_CS_Skin });
		break;
	case MP_CustomData0:
		Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Hair, MSM_Cloth, MSM_Eye, MSM_SubsurfaceProfile });
		break;
	case MP_CustomData1:
		Active = ShadingModels.HasAnyShadingModel({ MSM_ClearCoat, MSM_Eye });
		break;

// [...]

}

 

  • Opacity 의 경우 getter IsSubsurfaceShadingModel() 로 처리되는 것 같습니다 . 
  • 여기에 지하 Celshading을 추가합니다( 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_CS_Subsurface) || ShadingModel.HasShadingModel(MSM_CS_Skin);
}

 

핀에 대해 알아두면 좋은 점

  • 핀 이름(예: customdata0 )을 더 나은 이름으로 바꿔야 하는 경우 해당 이름은 MaterialShared.cpp 파일 과 FMaterialAttributeDefinitionMap::GetAttributeOverrideForMaterial() 함수 내에 있습니다 .

 

FText FMaterialAttributeDefinitionMap::GetAttributeOverrideForMaterial(const FGuid& AttributeID, UMaterial* Material)
{
	TArray<TKeyValuePair<EMaterialShadingModel, FString>> CustomPinNames;
	EMaterialProperty Property = GMaterialPropertyAttributesMap.Find(AttributeID)->Property;

	switch (Property)
	{

// [...]

	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" });
		return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, "Custom Data 0"));
	case MP_CustomData1:
		CustomPinNames.Add({ MSM_ClearCoat, "Clear Coat Roughness" });
		CustomPinNames.Add({MSM_Eye, "Iris Distance"});
		return FText::FromString(GetPinNameFromShadingModelField(Material->GetShadingModels(), CustomPinNames, "Custom Data 1"));

// [...]

}

 

  • Refraction 과 같은 핀을 빌리려면 예, 사용할 수 있지만 아무 것도 적혀 있지 않습니다. 
  • 왜냐하면 UE는 자료를 번역할 때 중복되거나 지원되지 않는 부분이 있는지 확인한 후 "지원되지 않는" 부분을 삭제하거나 기본값으로 대체하기 때문입니다. 
  • 이를 위해서는 HLSLMaterialTranslator.cpp 파일 내에서 Translate() 함수의 이 조건을 변경해야 합니다 .

 

bool FHLSLMaterialTranslator::Translate()
{
	TRACE_CPUPROFILER_EVENT_SCOPE(FHLSLMaterialTranslator::Translate);

	// [...]

	if (IsTranslucentBlendMode(BlendMode) || MaterialShadingModels.HasShadingModel(MSM_SingleLayerWater))
	{
		int32 UserRefraction = ForceCast(Material->CompilePropertyAndSetMaterialProperty(MP_Refraction, this), MCT_Float1);
		int32 RefractionDepthBias = ForceCast(ScalarParameter(FName(TEXT("RefractionDepthBias")), Material->GetRefractionDepthBiasValue()), MCT_Float1);

		Chunk[MP_Refraction] = AppendVector(UserRefraction, RefractionDepthBias);
	}

// [...]

}

 

  • 현재는 반투명 단일 레이어 물 재질만 굴절 핀에 쓸 수 있습니다.
  • 우리는 C++ 부분을 마쳤습니다.
  • 컴파일하면 셰이딩 모델을 변경하고 핀 활성화/비활성화를 볼 수 있지만 물론 모든 것이 검은색으로 나타납니다. 

 

참고자료

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

https://docs.unrealengine.com/5.3/ko/render-dependency-graph-in-unreal-engine/

 

렌더 종속성 그래프

렌더 명령을 컴파일하고 실행할 수 있도록 그래프 데이터 구조체로 기록하는 즉시 모드 API입니다.

docs.unrealengine.com

 

728x90
반응형