플러그인 내 셰이더 개요

플러그인에 셰이더를 만드는 법입니다.

Windows
MacOS
Linux

이 글은 HLSL 코드나 GPU 효율적인 셰이더 작성 방법이 아닌 플러그인 시스템을 사용하여 새로운 셰이더를 만드는 법을 알려드리기 위한 글이라는 점 참고해 주시기 바랍니다.

언리얼 엔진 4 (UE4) 에서 사용할 셰이더 코드를 새로 추가하는 작업이 이제 플러그인 시스템을 통해서도 가능해졌습니다. 플러그인 시스템을 통해 셰이더를 만들면 기존의 것을 원하는 대로 쉽고 빠르게 공유할 수 있습니다. 여기서는 UE4 의 플러그인으로 셰이더를 생성하는 데 필요한 작업을 하이 레벨에서 살펴보도록 하겠습니다.

추가적인 도움말은, //Engine/Plugins/Compositing/LensDistortion 플러그인을 직접 살펴보시기 바랍니다.

플러그인 제작 팁

새 플러그인을 만들 때 유의해야 할 점은 다음과 같습니다:

  • 플러그인 마법사를 사용하면 플러그인에 필요한 모든 파일과 폴더를 빠르게 만들 수 있습니다.

  • 현재 플러그인을 통해 머티리얼에 셰이더 모델을 추가하거나 하는 등의 큰 변화는 가능하지 않습니다.

  • 모든 파일과 폴더가 필수 위치에 추가되었는지 확인한 후 Visual Studio 솔루션 파일을 재생성하세요.

  • ProjectName.uplugin 에서 (셰이더 구현을 가질 모듈에 대해서만) 모듈의 LoadingPhase 가 PostConfigInit 인지 확인하세요. 다음 예제와 같습니다:

    {
        "FileVersion" : 3,
        "Version" : 1,
        "VersionName" : "1.0",
        "FriendlyName" : "Foo",
        "Description" : "Plugin to play around with shaders.",
        "Category" : "Sandbox",
        "CreatedBy" : "Epic Games, Inc.",
        "CreatedByURL" : "http://epicgames.com",
        "DocsURL" : "",
        "MarketplaceURL" : "",
        "SupportURL" : "",
        "EnabledByDefault" : false,
        "CanContainContent" : true,
        "IsBetaVersion" : false,
        "Installed" : false,
        "Modules" :
        [
            {
                "Name" : "Foo",
                "Type" : "Developer",
                "LoadingPhase" : "PostConfigInit"
            }
        ]
    }

렌더 스레드

게임 측 API 와는 반대로, RHI 렌더 명령은 전용 스레드인 렌더링 스레드로 큐 등록(enque)을 (해야 )합니다. 렌더링 스레드는 게임 스레드의 슬레이브 스레드인데, 게임 스레드는 렌더링 스레드에 대해 ENQUEUE_RENDER_COMMAND 를 통해 FIFO (먼저 입력된 것을 먼저 출력하는) 명령으로 큐 등록하기 때문입니다. 렌더링 스레드는 게임 스레드보다 0-1 프레임 뒤쳐질 수 있습니다. CPU 퍼포먼스 측면에서, 그 둘 사이 동기화는 프로덕션 런타임에선 어떻게든 피해야 할 것입니다. 플러그인의 C++ 함수가 올바른 스레드에 호출되는지 확인하기 위해서는, check (IsInGameThread()); 또는 스레드 안정성이 향상된 check (IsInRenderingThread()); 와 같은 어서트를 여럿 추가하면 됩니다.

언리얼 셰이더 파일

UE4 에서 사용할 셰이더를 새로 개발할 때는 알아둬야 할 셰이더 파일 유형이 두 가지 있습니다. 각각 용도가 다른데, 다음과 같습니다:

  • 언리얼 셰이더 헤더 (.USH)

    • 다른 USH 또는 USF 파일에서만 포함됩니다.

  • 언리얼 셰이더 포맷 (.USF)

    • 프라이빗 데이터 전용입니다.

      • 프라이빗 디렉터리에 하위 호환성이 보장되지 않습니다.

    • 셰이더 엔트리 포인트가 있어야 합니다.

셰이더 파일 전처리 및 가상 파일 경로

USF 셰이더 파일은 HLSL 언어에 기반한 것으로, 멀티 플랫폼 셰이더 코드가 들어있는 언리얼 엔진 셰이더 파일 포맷입니다. 멀티 플랫폼 지원을 위해, 엔진의 셰이더 컴파일러에는 (FXC, GLSL 크로스 컴파일용 HLSLCC 등의) 플랫폼 전용 셰이더 컴파일러 앞에 별도의 플랫폼 독립적 소스 파일 전처리 패스가 추가되어 있습니다. 그 결과 모든 #define 과 #if 를 이 처음 전처리 단계에서 분석(resolve)합니다. 물론, 각 플랫폼에는 (VULKAN_PROFILE 과 같은) 내장 #define 이 있어 셰이더 전처리 타깃 플랫폼을 알 수 있습니다.

C/C++ 파일과 동일하게, #include "HelloWorld.usf" 로 usf 파일을 포함시킬 수 있는데, 그러면 #include 를 작성한 USF 파일과 같은 디렉터리에 저장된 HelloWorld.usf 파일을 포함시킵니다. 같은 파일을 여러 번 포함시키는 것을 피하기 위해서는, 파일 상단에 #pragma once 전처리 지시자를 사용하면 됩니다. 예:

  • FooCommon.usf

    // File shared across all Plugin's shaders
    #pragma once
    
    #include "/Engine/Public/Platform.ush"
    
    // ...
  • FooBar.usf

    // File containing all foobar-related functions and structures.
    #pragma once
    
    #include "FooCommon.usf"
    
    // ...
  • FooBarComputeShader.usf

    // Compute shader that does foobar on the GPU
    
    #include "FooCommon.usf"
    #include "FooBar.usf"
    
    // ...

    이와 같은 작업을 플러그인이나 프로젝트 모듈의 셰이더에서 하여 USF 파일을 포함시킬 수 있는데, 방법은 다음 둘 중 하나입니다:

  • 엔진에서 #include /Engine/<FilePath> 를 사용하며, <FilePath>//Engine/Shaders/ 디렉터리 기준 경로입니다.

  • 또는 다른 플러그인에서 #include /Plugin/<PluginName>/<PluginFilePath> 를 사용하며, <PluginName>활성화된 플러그인 이름, <PluginFilePath> 는 플러그인의 Shaders/ 디렉터리 기준 파일 경로입니다. .uplugin 의 올바른 플러그인에 대한 종속성을 추가하는 것은 개발자 책임입니다.

첫 글로벌 셰이더

글로벌 셰이더가 FGlobalShader 에서 상속받는 방식은 다음과 같습니다:

class FLensDistortionUVGenerationShader : public FGlobalShader
{
public:
    // This function determines whether or not the shader should be compiled for a given platform.
    // In this case, the shader will not be usable without SM4 support.
    static bool ShouldCache(EShaderPlatform Platform)
    {
        return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4);
    }

    // Compile-time constants for shader can be defined in this function:
    static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
    {
        FGlobalShader::ModifyCompilationEnvironment(Platform, OutEnvironment);
        OutEnvironment.SetDefine(TEXT("GRID_SUBDIVISION_X"), kGridSubdivisionX);
        OutEnvironment.SetDefine(TEXT("GRID_SUBDIVISION_Y"), kGridSubdivisionY);
    }

    // Default constructor.
    FLensDistortionUVGenerationShader() {}

    // Constructor using an initializer object. We can bind parameters here so that our C++ code
    // can interface with USF, enabling us to set shader parameters from code.
    FLensDistortionUVGenerationShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
        : FGlobalShader(Initializer)
    {
        PixelUVSize.Bind(Initializer.ParameterMap, TEXT("PixelUVSize"));
        RadialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT("RadialDistortionCoefs"));
        TangentialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT("TangentialDistortionCoefs"));
        DistortedCameraMatrix.Bind(Initializer.ParameterMap, TEXT("DistortedCameraMatrix"));
        UndistortedCameraMatrix.Bind(Initializer.ParameterMap, TEXT("UndistortedCameraMatrix"));
        OutputMultiplyAndAdd.Bind(Initializer.ParameterMap, TEXT("OutputMultiplyAndAdd"));
    }

    // All members must be serialized here. This function is run at load and save time, and is used
    // to put the shader into the DDC and pak files.
    virtual bool Serialize(FArchive& Ar) override
    {
        bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
        Ar << PixelUVSize << RadialDistortionCoefs << TangentialDistortionCoefs << DistortedCameraMatrix << UndistortedCameraMatrix << OutputMultiplyAndAdd;
        return bShaderHasOutdatedParameters;
    }

    // This function is an example of how shader parameters can be precomputed based on data specific
    // to the shader. In this case, the shader requires several matrices that can be computed from
    // a few parameters, and that would be inefficient to compute inside the shader itself. Note that
    // this function is not an override; it is custom to this class, and is called when this feature's
    // specific implementation requires it.
    template<typename TShaderRHIParamRef>
    void SetParameters(
        FRHICommandListImmediate& RHICmdList,
        const TShaderRHIParamRef ShaderRHI,
        const FCompiledCameraModel& CompiledCameraModel,
        const FIntPoint& DisplacementMapResolution)
    {
        FVector2D PixelUVSizeValue(
            1.f / float(DisplacementMapResolution.X), 1.f / float(DisplacementMapResolution.Y));
        FVector RadialDistortionCoefsValue(
            CompiledCameraModel.OriginalCameraModel.K1,
            CompiledCameraModel.OriginalCameraModel.K2,
            CompiledCameraModel.OriginalCameraModel.K3);
        FVector2D TangentialDistortionCoefsValue(
            CompiledCameraModel.OriginalCameraModel.P1,
            CompiledCameraModel.OriginalCameraModel.P2);

        SetShaderValue(RHICmdList, ShaderRHI, PixelUVSize, PixelUVSizeValue);
        SetShaderValue(RHICmdList, ShaderRHI, DistortedCameraMatrix, CompiledCameraModel.DistortedCameraMatrix);
        SetShaderValue(RHICmdList, ShaderRHI, UndistortedCameraMatrix, CompiledCameraModel.UndistortedCameraMatrix);
        SetShaderValue(RHICmdList, ShaderRHI, RadialDistortionCoefs, RadialDistortionCoefsValue);
        SetShaderValue(RHICmdList, ShaderRHI, TangentialDistortionCoefs, TangentialDistortionCoefsValue);
        SetShaderValue(RHICmdList, ShaderRHI, OutputMultiplyAndAdd, CompiledCameraModel.OutputMultiplyAndAdd);
    }

private:
    // Shader parameters.
    FShaderParameter PixelUVSize;
    FShaderParameter RadialDistortionCoefs;
    FShaderParameter TangentialDistortionCoefs;
    FShaderParameter DistortedCameraMatrix;
    FShaderParameter UndistortedCameraMatrix;
    FShaderParameter OutputMultiplyAndAdd;
};

// This macro exposes the shader to the Engine. Note the absolute virtual source file path.
IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationVS, TEXT("/Plugin/LensDistortion/Private/UVGeneration.usf"), TEXT("MainVS"), SF_Vertex)

Engine/Public/Platform.usf

셰이더가 모든 UE4 플랫폼에서 컴파일되도록 하기 위해서는, 모든 셰이더 파일에 (직간접적으로) /Engine/Public/Platform.usf 파일을 포함시켜야 합니다.

셰이더 개발 팁

로컬에서 ConsoleVariables.ini 를 사용하여 렌더러의 세팅을 변경하여 셰이더 작성 시 이터레이션 프로세스를 가속시키는 등의 커스터마이징이 가능합니다. 예를 들어 현재 셰이더가 하는 작업 관련 자세한 디버그 정보를 구하는 데 도움이 되는 콘솔 변수는 다음과 같습니다:

  • r.ShaderDevelopmentMode = 1 셰이더 컴파일 시 자세한 로그를 구하여 오류 발생 시 재시도할 기회를 노릴 수 있습니다.

  • r.DumpShaderDebugInfo = 1 전처리 셰이더를 Saved 폴더에 덤핑합니다.

    경고: 이 옵션을 켠 채로 놔두면 하드 디스크가 작은 파일과 폴더로 가득차므로, 완료 후 끄도록 하세요.

문제해결

셰이더를 컴파일하여 UE4 에디터에 나타나도록 하는 과정에서 문제가 생긴 경우, 다음과 같은 방법을 시도해 보세요:

  • Can't compile: /Plugin/<MyPluginName>/<MyFile> not found. 오류가 나는 경우, 플러그인 모듈의 LoadingPhase 가 PostConfigInit 로 설정되었는지, 플러그인 Shaders/ 디렉터리 이름에 오타는 없는지 확인하세요.

  • Can't #include "/Plugin/<ParentPluginName>/<MyFile>": 오류가 나는 경우, 부모 플러그인이 활성화되었는지, 그리고 플러그인 종속성도 확인하세요. .uplugin 또는 .uproject 에 플러그인 종속성이 없다는 뜻이기 때문입니다.

기존 렌더러 작명규칙

렌더러에서, 셰이더 클래스 및 셰이더 엔트리 포인트 이름을 짓는 데 일정한 규칙이 있었는데, 특히 셰이더 도메인 접미사 관련해서 다음 표와 같습니다:

셰이더 도메인

접미사

Vertex Shader

VS

Hull Shader

HS

Domain Shader

DS

Geometry Shader

GS

Pixel Shader

PS

Compute Shader

CS

예를 들어, C++ 파일에서, FLensDistortionUVGenerationVS 호출 끝에 붙은 VS 는 버텍스 셰이더의 약자입니다. USF 파일 네 void MainVS(...) 끝에 붙은 VS 는 버텍스 셰이더를 사용할 것임을 나타냅니다. HLSL 구조체 관련해서도, FBasePassInterpolators 처럼 구조체 이름이 F 로 시작합니다.

UE4 코딩 표준 관련 자세한 내용은 코딩 표준 문서를 참고하세요.

부가 링크

UE4 안에서 글로벌 셰이더를 개발하는 데 관련된 상세 정보 링크입니다.

태그
Select Skin
Light
Dark

새로운 언리얼 엔진 4 문서 사이트에 오신 것을 환영합니다!

문서 사이트에 대한 의견을 모을 수 있는 피드백 시스템을 포함해서 여러가지 새로운 기능을 준비하고 있습니다. 아래 Documentation Feedback 포럼(영문) 또는 언리얼 엔진 네이버 공식 카페(한글) 중 편하신 곳에 의견이나 문제점을 알려 주세요.

새 시스템이 준비되면 알려 드리겠습니다.

네이버 카페
공식 포럼