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++ 부분을 마쳤습니다.
- 컴파일하면 셰이딩 모델을 변경하고 핀 활성화/비활성화를 볼 수 있지만 물론 모든 것이 검은색으로 나타납니다.
참고자료
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
반응형