역자의 말.
경험상 노드 기반의 머트리얼이 노드 자체가 가독성도 너무 떨어지고 데이터 플로우 트레이스 하기에도 딱히 좋지도 않고(개인적으로...) 말이죠.
너무 복잡한 스파게티 노드는 함수화 하고 그 안에서도 익스프레션을 혼용해서 조금이라도 유지관리를 도모 할 수도 있고 간단히 셰이더 평션 유닛 테스트 할 때는 유용하게 쓰일 수 있고 특정 효과를 코드 빌드 없이 추가할 때는 이 방법을 주로 선호하게 될 수도 있겠죠.
머트리얼 에디터는 일반사용자에게 블랙박스와 같은 것이기도 하고요.
가능하다면 효과 테스트 후 릴리스 빌드 안에 추가 하고 머트리얼 인터페이스 코드를 수정 해서 좀 더 사용자 친화적으로 개진 하는 것이 올바르다고 봅니다.
커스텀 익스프레션은 노드 자체에 코드를 작성 하는 방법과 .usf 파일로 저장 후 읽어 들여서 파싱하는 방식 이렇게 두 가지가 있습니다. 후자의 코드 관리 방법은 프로그래머의 도움을 받거나 테크 아티스트 도움을 받길 추천 합니다. 개인적으로 리마인드 할 겸 하여 커스터 익스프레션 사용할 때 읽어볼만한 블로그 포스팅이 있기에 간단히 번역하여 올려봅니다.
원문. ( 빅터의 테크아트 블로그 )
Unreal Engine Custom Node
최근 저는 레이마칭 기법을 가지고 놀면서 언리얼 엔진 머티리얼 에디터에서 이를 구현해 보려고 합니다. 이 알고리즘에는 for 루프가 필요하기 때문에 커스텀 노드가 있는 HLSL 코드만 사용할 수 있습니다.
특히 코드가 복잡해질 때 커스텀 노드를 사용하면 많은 함정이 있습니다. 디테일 패널 코드 에디터는 매우 원시적이어서 VSCode처럼 코드를 편집하고 싶었고, 패널에 더 많은 옵션이 추가되었지만 전에는 거의 사용하지 않았기 때문에 나중에 참조할 수 있도록 제가 연구하고 테스트한 내용을 문서화했습니다.
새로운 사용자 지정 노드 옵션
예:
생성된 코드는 아래와 같습니다. (머티리얼 에디터의 창 -> HLSL 코드에서 생성된 코드를 확인하세요.) FMaterialPixelParameters 파라미터는 컴파일러에서 추가 비밀 입력으로 전달된 것을 참고하세요.
#ifndef additional_defines
#define additional_defines 42
#endif//additional_defines
MaterialFloat3 CustomExpression0(
FMaterialPixelParameters Parameters,
MaterialFloat input,
inout MaterialFloat a,
inout MaterialFloat b)
{
a = input;
b = a+1;
return b;
}
최종 셰이더에 포함되는 모든 자동 생성 상용구 코드를 볼 수 있으므로 문제 해결에 매우 유용합니다. C:\Program Files\Epic Games\UE_4.27\Engine\Shaders에서도 코드를 확인할 수 있으며, 여기에는 많은 유용한 내용이 있습니다.
다음 포스트에서 더 자세히 알아보세요! :)
글로벌 함수 만들기(핵!)
컴파일러는 말 그대로 Custom 노드의 텍스트를 CustomExpressionX라는 함수에 다음과 같이 복사하여 붙여넣습니다:
MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters)
{
return 1; // return 1 is essential!
}
이를 기반으로 다음과 같은 코드를 입력하면 해킹을 수행할 수 있습니다:
//MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters)
//{
return 1;
}
float MyGlobalVariable;
float MyGlobalFunction(float a)
{
return a;
//}
중괄호로 함수를 세분화하고 글로벌 네임스페이스에 MyGlobalVariable 및 MyGlobalFunction()을 정의하는 트릭을 볼 수 있습니다. (엉성하지만 멋지죠!)
#include shader code ( 셰이더 코드 포함 )
이슈
당연히 디테일 패널에서 파일 경로 포함 옵션을 사용하고 싶습니다. 하지만 이렇게 하면 셰이더를 더미 경로 /Project/a.usf에 넣는 경우와 같은 오류가 발생하기 쉽습니다:
[SM5] Can't map virtual shader source path "/Project/a.usf".
Directory mappings are:
/Engine -> D:/Program Files/Epic Games/UE_4.27/Engine/Shaders
[SM5] /Engine/Generated/Material.ush(2080): error: Can't open include file "/Project/a.usf"
#include "/Project/a.usf"
from /Engine/Private/BasePassVertexCommon.ush: 15: #include "/Engine/Generated/Material.ush"
from /Engine/Private/BasePassVertexShader.usf: 7: #include "BasePassVertexCommon.ush"
from /Engine/Private/BasePassPixelShader.usf: 38: #include "/Engine/Generated/Material.ush"
[SM5] 1 error in preprocessor.
UE는 커스텀 노드 hlsl 코드(.usf 또는 .ush)를 /Engine -> D:/Program Files/Epic Games/UE_4.27/Engine/Shaders에 넣으라고 하는데, 셰이더를 프로젝트와 함께 제공받고 싶으면 어떻게 해야 하나요?
C++ 코드 수정
새 더미 C++ 클래스를 생성하여 현재 프로젝트를 C++ 프로젝트로 변환하고 컴파일합니다(저는 Actor 베이스 클래스에서 상속된 MyActor라는 클래스를 생성했습니다.).
프로젝트가 Visual Studio에 올바르게 로드되고 컴파일되려면 .uproject 파일 아이콘을 마우스 오른쪽 버튼으로 클릭하고 Visual Studio 프로젝트 파일 생성 을 선택해야 할 수 있습니다.
프로젝트 디렉터리의 Content 폴더와 같은 레벨에 Shaders라는 폴더를 생성합니다.
<project>.build.cs 파일의 공용 종속성 모듈 배열에 RenderCore를 추가합니다:
using UnrealBuildTool;
public class ShaderBits : ModuleRules
{
public ShaderBits(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "RenderCore" }); // add "RenderCore"!
PrivateDependencyModuleNames.AddRange(new string[] { });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
In .h file add a new module with a StartupModule() function overrides:
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h" // include this!
class FShaderBitsModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};
<project_name>.cpp에서 추가된 셰이더 경로의 정의와 함께 StartupModule() 오버라이드를 추가하고 이 새 경로를 /Project로 매핑합니다.
마지막으로 매크로 IMPLEMENT_PRIMARY_GAME_MODULE에서 FDefaultGameModuleImpl을 FShaderBitsModule로 대체합니다.
#include "ShaderBits.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IPluginManager.h"
#include "Logging/LogMacros.h"
#include "Misc/Paths.h" // include this!
void FShaderBitsModule::StartupModule()
{
//#if (ENGINE_MINOR_VERSION >= 21)
FString ShaderDirectory = FPaths::Combine(FPaths::ProjectDir(), TEXT("Shaders")); // add this!
AddShaderSourceDirectoryMapping("/Project", ShaderDirectory);
//#endif
}
void FShaderBitsModule::ShutdownModule(){}
//IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, ShaderBits, "ShaderBits" );
IMPLEMENT_PRIMARY_GAME_MODULE(FShaderBitsModule, Project, "Project"); // edit this!
셰이더 코드 포함
그런 다음 /<프로젝트>/셰이더에서 셰이더를 만들 수 있습니다:
를 클릭하고 셰이더 파일을 포함합니다:
함수를 명시적으로 호출해야 할 수도 있습니다.
또는 코드 블록에 직접 포함할 수도 있습니다:
#include "/Project/ShaderPathTest/MyShaderStatement.usf"
return 1;
이 경우 컴파일러에 유효한 함수임을 알리기 위해 반환값 1이 필요합니다.
생성된 코드를 확인하면 뉘앙스의 차이를 확인할 수 있습니다:
MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters,MaterialFloat4 SceneTexture)
{
#include "/Project/ShaderPathTest/MyShaderStatement.usf"
return 1;
}
#include "/Project/ShaderPathTest/MyShaderFunction.usf"
MaterialFloat3 CustomExpression0(FMaterialPixelParameters Parameters,MaterialFloat4 SceneTexture)
{
return func(SceneTexture,Parameters);;
}
그런 다음 외부 편집기에서 코드를 편집하고 저장한 후 머티리얼 편집기에서 코드에 줄 바꿈을 추가(Shift-enter)하여 다시 컴플라이언스를 트리거하고 적용 버튼을 클릭합니다.
UE 커스텀 노드 안에서 여러 함수 정의하기
커스텀 노드는 CustomExpression#() 함수 안에 코드를 래핑하므로 일반적으로 자체 함수를 정의하는 것이 금지됩니다.
하지만 HLSL에는 구조체 정의 안에 함수(메서드)를 정의할 수 있는 거의 문서화되지 않은 기능이 있는 것 같습니다.
구조체 정의는 함수 안에 중첩될 수 있습니다.
struct Functions
{
float3 OrangeBright(float3 c)
{
return c * float3(1, .7, 0);
}
float3 Out()
{
return OrangeBright(InColor);
}
};
Functions f;
return f.Out();
멋진 점은 이 모든 작업이 언리얼의 USF 파일을 방해하지 않고 자신만의 유효 네임스페이스 내에서 이루어지며, 컴파일 속도가 빨라 반복작업이 가능하다는 점입니다.
이제 커스텀 함수 라이브러리와 더 복잡한 셰이더를 빌드할 수 있습니다.
HLSL은 구조체 안에 중첩된 구조체를 정의하는 것을 금지하는 것 같으니, 커스텀 구조체는 함수 구조체 위와 외부에 정의해야 합니다.
Reference
- Creating a Gaussian Blur
- https://forums.unrealengine.com/t/extending-custom-hlsl-custom-expressions/88820
- https://forums.unrealengine.com/t/custom-hlsl-tips/104760
- https://odederell3d.blog/2021/03/22/ue4-loading-shaders-from-within-the-project-folder/
- https://forums.unrealengine.com/t/virtual-shader-source-path-link-custom-shaders-shadertoy-demo-download/119996
언리얼 엔진 5.0과 5.1의 경우 플러그인이 있습니다.
Shader Reader PRO in Code Plugins - UE Marketplace (unrealengine.com)
'TECH.ART.FLOW.IO' 카테고리의 다른 글
[간단정리?]Voxelizations of Shadow for mobile. Case Study. (0) | 2023.09.27 |
---|---|
[지인찬스]오랜 지인 김수빈님의 언리얼 바캉스 유투브 체널 오픈. (0) | 2023.09.26 |
[번역]Use Runtime Virtual Textures to Improve Landscape Performance in Unreal Engi (0) | 2023.09.21 |
[번역] Matcap 왜곡 보정 정보 (2) | 2023.09.20 |
[번역]Memory Statistics - Texture (0) | 2023.09.19 |