플러그인에 새로운 글로벌 셰이더 생성 - 코드 구성

글로벌 셰이더 플러그인용 플러그인 및 코드 구성 방법 안내입니다.

Windows
MacOS
Linux
목차

언리얼 엔진 4 (UE4) 용 신규 플러그인 제작을 시작하기에 앞서, 먼저 Visual Studio 가 설치되었는지 확인해야 합니다. Visual Studio 는 이 퀵 스타트에 필수인데, 플러그인 코드를 컴파일해야 실행할 수 있기 때문입니다. 그 방법이 확실치 않은 경우, 언리얼 엔진용 Visual Studio 구성 문서를 참고하세요.

  1. 먼저, 새로운 C++ Basic Code (기본 코드) 프로젝스를 만들고 데스크톱 세팅최대 퀄리티 로, 시작용 콘텐츠 는 포함시키지 않습니다.

    클릭하면 이미지 원본을 확인합니다.

  2. 프로젝트가 생성된 이후, Visual Studio 가 열리면 ShadersInPlugins 프로젝트에 우클릭하고 Build 옵션을 선택하여 프로젝트를 컴파일합니다.

    HT_ShadersInPlugins_01.png

  3. 프로젝트 컴파일이 완료되면, Visual Studio 에서 F5 키를 눌러 UE4 에디터에서 ShadersInPlugins 프로젝트를 실행시킵니다.

  4. UE4 에디터 로드가 완료되면, Plugins 매니저를 엽니다. 편집 > Plugins (플러그인)에서 플러그인 창 우하단의 New Plugin 옵션을 클릭하면 새 플러그인 생성 창이 뜹니다.

    클릭하면 이미지 원본을 확인합니다.

  5. 새 플러그인 창에서 Blank (기본) 플러그인을 선택하고 이름을 Foo 라 입력한 뒤, 기본 설정 그대로 놔둡니다. 그 작업이 전부 완료된 후 Create Plugin (플러그인 생성) 버튼을 클릭하면 플러그인에 필요한 초기 필수 콘텐츠가 전부 생성됩니다.

    클릭하면 이미지 원본을 확인합니다.

  6. 완료 후 UE4 와 Visual Studio 를 닫은 뒤 프로젝트 폴더 내 생성된 Plugins > Foo 플러그인 폴더로 이동합니다.

  7. Foo 플러그인 폴더 안에 새로운 폴더를 만들어 Shaders 라 하고, 그 안에 Private 이라는 폴더를 새로 만듭니다.

    클릭하면 이미지 원본을 확인합니다.

  8. Private 폴더 안에 txt 파일을 새로 만들고 MyShader.USF 라 한 뒤 다음 HLSL 코드를 그 파일 안에 붙여넣고 파일을 저장합니다.

    파일 확장자를 .USF 로 하지 않으면 UE4 가 셰이더 파일로 인식하지 못합니다.

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    /*=============================================================================
        LensDistortionUVGeneration.usf: Generate lens distortion and undistortion
        UV displacement map into a render target.
    
        The pixel shader directly compute the distort viewport UV to undistort
        viewport UV displacement using Sv_Position and the reference equations and
        store them into the red and green channels.
    
        However to avoid resolving with a ferrari method, or doing a newton method
        on the GPU to compute the undistort viewport UV to distort viewport UV
        displacement, this couple of shaders works as follow: The vertex shader
        undistort the grid's vertices, and pass down to the pixel shader the viewport
        UV of where they should have been on screen without undistortion. The pixel
        shader can then generate the undistort viewport UV to distort viewport UV
        displacement by just subtracting the pixel's viewport UV.
    =============================================================================*/
    
    #include "/Engine/Public/Platform.ush"
    
    // Size of the pixels in the viewport UV coordinates.
    float2 PixelUVSize;
    
    // K1, K2, K3
    float3 RadialDistortionCoefs;
    
    // P1, P2
    float2 TangentialDistortionCoefs;
    
    // Camera matrix of the undistorted viewport.
    float4 UndistortedCameraMatrix;
    
    // Camera matrix of the distorted viewport.
    float4 DistortedCameraMatrix;
    
    // Output multiply and add to the render target.
    float2 OutputMultiplyAndAdd;
    
    // Undistort a view position at V.z=1.
    float2 UndistortNormalizedViewPosition(float2 V)
    {
        float2 V2 = V * V;
        float R2 = V2.x + V2.y;
    
        // Radial distortion (extra parenthesis to match MF_Undistortion.uasset).
        float2 UndistortedV = V * (1.0 + R2 * (RadialDistortionCoefs.x + R2 * (RadialDistortionCoefs.y + R2 * RadialDistortionCoefs.z)));
    
        // Tangential distortion.
        UndistortedV.x += TangentialDistortionCoefs.y * (R2 + 2 * V2.x) + 2 * TangentialDistortionCoefs.x * V.x * V.y;
        UndistortedV.y += TangentialDistortionCoefs.x * (R2 + 2 * V2.y) + 2 * TangentialDistortionCoefs.y * V.x * V.y;
    
        return UndistortedV;
    }
    
    // Returns the undistorted viewport UV of the distorted's viewport UV.
    //
    // Notes:
    //        UVs are bottom left originated.
    float2 UndistortViewportUV(float2 ViewportUV)
    {
        // Distorted viewport UV -> Distorted view position (z=1)
        float2 DistortedViewPosition = (ViewportUV - DistortedCameraMatrix.zw) / DistortedCameraMatrix.xy;
    
        // Compute undistorted view position (z=1)
        float2 UndistortedViewPosition = UndistortNormalizedViewPosition(DistortedViewPosition);
    
        // Undistorted view position (z=1) -> Undistorted viewport UV.
        return UndistortedCameraMatrix.xy * UndistortedViewPosition + UndistortedCameraMatrix.zw;
    }
    
    // Flip UV's y component.
    float2 FlipUV(float2 UV)
    {
        return float2(UV.x, 1 - UV.y);
    }
    
    void MainVS(
        in uint GlobalVertexId : SV_VertexID,
        out float2 OutVertexDistortedViewportUV : TEXCOORD0,
        out float4 OutPosition : SV_POSITION
        )
    {
        // Compute the cell index.
        uint GridCellIndex = GlobalVertexId / 6;
    
        // Compute row and column id of the cell within the grid.
        uint GridColumnId = GridCellIndex / GRID_SUBDIVISION_Y;
        uint GridRowId = GridCellIndex - GridColumnId * GRID_SUBDIVISION_Y;
    
        // Compute the vertex id within a 2 triangles grid cell.
        uint VertexId = GlobalVertexId - GridCellIndex * 6;
    
        // Compute the bottom left originated UV coordinate of the triangle's vertex within the cell.
        float2 CellVertexUV = float2(0x1 & ((VertexId + 1) / 3), VertexId & 0x1);
    
        // Compute the top left originated UV of the vertex within the grid.
        float2 GridInvSize = 1.f / float2(GRID_SUBDIVISION_X, GRID_SUBDIVISION_Y);
        float2 GridVertexUV = FlipUV(
            GridInvSize * (CellVertexUV + float2(GridColumnId, GridRowId)));
    
        // The standard doesn't have half pixel shift.
        GridVertexUV -= PixelUVSize * 0.5;
    
        // Output vertex position.
        OutPosition = float4(FlipUV(
            UndistortViewportUV(GridVertexUV) + PixelUVSize * 0.5) * 2 - 1, 0, 1);
    
        // Output top left originated UV of the vertex.
        OutVertexDistortedViewportUV = GridVertexUV;
    }
    
    void MainPS(
        in noperspective float2 VertexDistortedViewportUV : TEXCOORD0,
        in float4 SvPosition : SV_POSITION,
        out float4 OutColor : SV_Target0
        )
    {
        // Compute the pixel's top left originated UV.
        float2 ViewportUV = SvPosition.xy * PixelUVSize;
    
        // The standard doesn't have half pixel shift.
        ViewportUV -= PixelUVSize * 0.5;
    
        float2 DistortUVtoUndistortUV = (UndistortViewportUV((ViewportUV))) - ViewportUV;
        float2 UndistortUVtoDistortUV = VertexDistortedViewportUV - ViewportUV;
    
        // Output displacement channels.
        OutColor = OutputMultiplyAndAdd.y + OutputMultiplyAndAdd.x * float4(
            DistortUVtoUndistortUV, UndistortUVtoDistortUV);
    }
  9. 이제 Foo.uplugin 파일을 찾아 텍스트 에디터에서 열고, 그 파일의 정보를 다음 텍스트로 대체한 뒤 저장합니다.

    {
        "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"
            }
        ]
    }
  10. Plugins\Foo\Source\Foo 로 이동하여 Classes 라는 폴더를 새로 만든 뒤 LensDistortionAPI.hLensDistortionBlueprintLibrary.h 파일을 Engine\Plugins\Compositing\LensDistortion 에서 새로 만든 폴더로 이동합니다.

    복사하려는 파일은 Engine\Plugins\Compositing\LensDistortion 에서 찾을 수 있습니다.

    • Classes - 새 폴더 생성

      • 복사 - LensDistortionAPI.h

      • 복사 - LensDistortionBlueprintLibrary.h

      HT_ShadersInPlugins_05.png

  11. Private 폴더로 이동한 뒤 LensDistortionBlueprintLibrary.cppLensDistortionRendering.cpp 파일을 이 Private 폴더로 복사합니다.

    • Private - 기존 폴더

      • 복사 - LensDistortionBlueprintLibrary.cpp

      • 복사 - LensDistortionRendering.cpp

      HT_ShadersInPlugins_06.png

  12. 이제 UE4 에디터와 Visual Studio 를 닫은 뒤 .U 프로젝트 파일 위치로 이동합니다. 그 위에 우클릭하고 Generate Visual Studio project files 옵션을 선택합니다.

    HT_ShadersInPlugins_07.png

  13. Visual Studio 솔루션을 다시 연 뒤 Foo > Classes 로 이동하여 LensDistortionAPI.h 파일을 엽니다. 이 파일 안에서 FLensDistortionCameraModelFFooCameraModel 로 대체합니다.

    이 파일에서 FLensDistortionCameraModel 을 FFooCameraModel 로 네 번 대체해야 합니다.

        // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
        #pragma once
    
        #include "CoreMinimal.h"
        #include "LensDistortionAPI.generated.h"
    
        /** Mathematic camera model for lens distortion/undistortion.
         *
         * Camera matrix =
         *  | F.X  0  C.x |
         *  |  0  F.Y C.Y |
         *  |  0   0   1  |
         */
        USTRUCT(BlueprintType)
        struct FFooCameraModel
        {
            GENERATED_USTRUCT_BODY()
            FFooCameraModel()
            {
                K1 = K2 = K3 = P1 = P2 = 0.f;
                F = FVector2D(1.f, 1.f);
                C = FVector2D(0.5f, 0.5f);
            }
    
            /** Radial parameter #1. */
            UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Camera Model")
            float K1;
    
            /** Radial parameter #2. */
            UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Camera Model")
            float K2;
    
            /** Radial parameter #3. */
            UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Camera Model")
            float K3;
    
            /** Tangential parameter #1. */
            UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Camera Model")
            float P1;
    
            /** Tangential parameter #2. */
            UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Camera Model")
            float P2;
    
            /** Camera matrix's Fx and Fy. */
            UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Camera Model")
            FVector2D F;
    
            /** Camera matrix's Cx and Cy. */
            UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Camera Model")
            FVector2D C;
    
            /** Undistorts 3d vector (x, y, z=1.f) in the view space and returns (x', y', z'=1.f). */
            FVector2D UndistortNormalizedViewPosition(FVector2D V) const;
    
            /** Returns the overscan factor required for the undistort rendering to avoid unrendered distorted pixels. */
            float GetUndistortOverscanFactor(
                float DistortedHorizontalFOV,
                float DistortedAspectRatio) const;
    
            /** Draws UV displacement map within the output render target.
             * - Red & green channels hold the distortion displacement;
             * - Blue & alpha channels hold the undistortion displacement.
             * @param World Current world to get the rendering settings from (such as feature level).
             * @param DistortedHorizontalFOV The desired horizontal FOV in the distorted render.
             * @param DistortedAspectRatio The desired aspect ratio of the distorted render.
             * @param UndistortOverscanFactor The factor of the overscan for the undistorted render.
             * @param OutputRenderTarget The render target to draw to. Don't necessarily need to have same resolution or aspect ratio as distorted render.
             * @param OutputMultiply The multiplication factor applied on the displacement.
             * @param OutputAdd Value added to the multiplied displacement before storing the output render target.
             */
            void DrawUVDisplacementToRenderTarget(
                class UWorld* World,
                float DistortedHorizontalFOV,
                float DistortedAspectRatio,
                float UndistortOverscanFactor,
                class UTextureRenderTarget2D* OutputRenderTarget,
                float OutputMultiply,
                float OutputAdd) const;
    
            /** Compare two lens distortion models and return whether they are equal. */
            bool operator == (const FFooCameraModel& Other) const
            {
                return (
                    K1 == Other.K1 &&
                    K2 == Other.K2 &&
                    K3 == Other.K3 &&
                    P1 == Other.P1 &&
                    P2 == Other.P2 &&
                    F == Other.F &&
                    C == Other.C);
            }
    
            /** Compare two lens distortion models and return whether they are different. */
            bool operator != (const FFooCameraModel& Other) const
            {
                return !(*this == Other);
            }
        };
  14. LensDistortionBlueprintLibrary.h 파일을 엽니다. 이 파일은 블루프린트에서 이 노드가 표시되는 방식을 제어하므로, FLensDistortionCameraModelFFooCameraModel 로 대체하는 것과 아울러 Category = "Lens Distortion"Category = "Foo | Lens Distortion" 로 변경해야 합니다.

    이 파일에서 FLensDistortionCameraModel 을 FFooCameraModel 로 여섯 번 대체합니다.

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "UObject/ObjectMacros.h"
    #include "Classes/Kismet/BlueprintFunctionLibrary.h"
    #include "LensDistortionAPI.h"
    #include "LensDistortionBlueprintLibrary.generated.h"
    
    UCLASS(MinimalAPI)
    class ULensDistortionBlueprintLibrary : public UBlueprintFunctionLibrary
    {
        GENERATED_UCLASS_BODY()
    
        /** Returns the overscan factor required for the undistort rendering to avoid unrendered distorted pixels. */
        UFUNCTION(BlueprintPure, Category = "Foo | Lens Distortion")
        static void GetUndistortOverscanFactor(
            const FFooCameraModel& CameraModel,
            float DistortedHorizontalFOV,
            float DistortedAspectRatio,
            float& UndistortOverscanFactor);
    
        /** Draws UV displacement map within the output render target.
         * - Red & green channels hold the distortion displacement;
         * - Blue & alpha channels hold the undistortion displacement.
         * @param DistortedHorizontalFOV The desired horizontal FOV in the distorted render.
         * @param DistortedAspectRatio The desired aspect ratio of the distorted render.
         * @param UndistortOverscanFactor The factor of the overscan for the undistorted render.
         * @param OutputRenderTarget The render target to draw to. Don't necessarily need to have same resolution or aspect ratio as distorted render.
         * @param OutputMultiply The multiplication factor applied on the displacement.
         * @param OutputAdd Value added to the multiplied displacement before storing into the output render target.
         */
        UFUNCTION(BlueprintCallable, Category = "Foo | Lens Distortion", meta = (WorldContext = "WorldContextObject"))
        static void DrawUVDisplacementToRenderTarget(
            const UObject* WorldContextObject,
            const FFooCameraModel& CameraModel,
            float DistortedHorizontalFOV,
            float DistortedAspectRatio,
            float UndistortOverscanFactor,
            class UTextureRenderTarget2D* OutputRenderTarget,
            float OutputMultiply = 0.5,
            float OutputAdd = 0.5
            );
    
        /* Returns true if A is equal to B (A == B) */
        UFUNCTION(BlueprintPure, meta=(DisplayName = "Equal (LensDistortionCameraModel)", CompactNodeTitle = "==", Keywords = "== equal"), Category = "Foo | Lens Distortion")
        static bool EqualEqual_CompareLensDistortionModels(
            const FFooCameraModel& A,
            const FFooCameraModel& B)
        {
            return A == B;
        }
    
        /* Returns true if A is not equal to B (A != B) */
        UFUNCTION(BlueprintPure, meta = (DisplayName = "NotEqual (LensDistortionCameraModel)", CompactNodeTitle = "!=", Keywords = "!= not equal"), Category = "Foo | Lens Distortion")
        static bool NotEqual_CompareLensDistortionModels(
            const FFooCameraModel& A,
            const FFooCameraModel& B)
        {
            return A != B;
        }
    };
  15. Private 폴더로 이동한 뒤 LensDistortionBlueprintLibrary.cpp 파일을 열고 다음과 같이 대체합니다:

    • FLensDistortionCameraModelFFooCameraModel

    • ULensDistortionBlueprintLibraryUFooBlueprintLibrary

    FLensDistortionCameraModel 에서 FFooCameraModel 은 번, ULensDistortionBlueprintLibrary 에서 UFooBlueprintLibrary 는 번입니다.

        // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
        #include "LensDistortionBlueprintLibrary.h"
    
        ULensDistortionBlueprintLibrary::ULensDistortionBlueprintLibrary(const FObjectInitializer& ObjectInitializer)
            : Super(ObjectInitializer)
        { }
    
        // static
        void ULensDistortionBlueprintLibrary::GetUndistortOverscanFactor(
            const FFooCameraModel& CameraModel,
            float DistortedHorizontalFOV,
            float DistortedAspectRatio,
            float& UndistortOverscanFactor)
        {
            UndistortOverscanFactor = CameraModel.GetUndistortOverscanFactor(DistortedHorizontalFOV, DistortedAspectRatio);
        }
    
        // static
        void ULensDistortionBlueprintLibrary::DrawUVDisplacementToRenderTarget(
            const UObject* WorldContextObject,
            const FFooCameraModel& CameraModel,
            float DistortedHorizontalFOV,
            float DistortedAspectRatio,
            float UndistortOverscanFactor,
            class UTextureRenderTarget2D* OutputRenderTarget,
            float OutputMultiply,
            float OutputAdd)
        {
            CameraModel.DrawUVDisplacementToRenderTarget(
                WorldContextObject->GetWorld(),
                DistortedHorizontalFOV, DistortedAspectRatio,
                UndistortOverscanFactor, OutputRenderTarget,
                OutputMultiply, OutputAdd);
        }
  16. Private 폴더에서 LensDistortionRendering.cpp 파일을 열고 FLensDistortionCameraModelFFooCameraModel 로 대체합니다.

    You should replace FLensDistortionCameraModel with FFooCameraModel six times in this file.

        // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
        #include "LensDistortionAPI.h"
        #include "Classes/Engine/TextureRenderTarget2D.h"
        #include "Classes/Engine/World.h"
        #include "Public/GlobalShader.h"
        #include "Public/PipelineStateCache.h"
        #include "Public/RHIStaticStates.h"
        #include "Public/SceneUtils.h"
        #include "Public/SceneInterface.h"
        #include "Public/ShaderParameterUtils.h"
    
        static const uint32 kGridSubdivisionX = 32;
        static const uint32 kGridSubdivisionY = 16;
    
        /**
         * Internal intermediary structure derived from FFooCameraModel by the game thread
         * to hand to the render thread.
         */
        struct FCompiledCameraModel
        {
            /** Orignal camera model that has generated this compiled model. */
            FFooCameraModel OriginalCameraModel;
    
            /** Camera matrices of the lens distortion for the undistorted and distorted render.
             *  XY holds the scales factors, ZW holds the translates.
             */
            FVector4 DistortedCameraMatrix;
            FVector4 UndistortedCameraMatrix;
    
            /** Output multiply and add of the channel to the render target. */
            FVector2D OutputMultiplyAndAdd;
        };
    
        /** Undistorts top left originated viewport UV into the view space (x', y', z'=1.f) */
        static FVector2D LensUndistortViewportUVIntoViewSpace(
            const FFooCameraModel& CameraModel,
            float TanHalfDistortedHorizontalFOV, float DistortedAspectRatio,
            FVector2D DistortedViewportUV)
        {
            FVector2D AspectRatioAwareF = CameraModel.F * FVector2D(1, -DistortedAspectRatio);
            return CameraModel.UndistortNormalizedViewPosition((DistortedViewportUV - CameraModel.C) / AspectRatioAwareF);
        }
    
        class FLensDistortionUVGenerationShader : public FGlobalShader
        {
        public:
            static bool ShouldCache(EShaderPlatform Platform)
            {
                return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4);
            }
    
            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);
            }
    
            FLensDistortionUVGenerationShader() {}
    
            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"));
            }
    
            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);
            }
    
            virtual bool Serialize(FArchive& Ar) override
            {
                bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
                Ar << PixelUVSize << RadialDistortionCoefs << TangentialDistortionCoefs << DistortedCameraMatrix << UndistortedCameraMatrix << OutputMultiplyAndAdd;
                return bShaderHasOutdatedParameters;
            }
    
        private:
            FShaderParameter PixelUVSize;
            FShaderParameter RadialDistortionCoefs;
            FShaderParameter TangentialDistortionCoefs;
            FShaderParameter DistortedCameraMatrix;
            FShaderParameter UndistortedCameraMatrix;
            FShaderParameter OutputMultiplyAndAdd;
    
        };
    
        class FLensDistortionUVGenerationVS : public FLensDistortionUVGenerationShader
        {
            DECLARE_SHADER_TYPE(FLensDistortionUVGenerationVS, Global);
    
        public:
    
            /** Default constructor. */
            FLensDistortionUVGenerationVS() {}
    
            /** Initialization constructor. */
            FLensDistortionUVGenerationVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
                : FLensDistortionUVGenerationShader(Initializer)
            {
            }
        };
    
        class FLensDistortionUVGenerationPS : public FLensDistortionUVGenerationShader
        {
            DECLARE_SHADER_TYPE(FLensDistortionUVGenerationPS, Global);
    
        public:
    
            /** Default constructor. */
            FLensDistortionUVGenerationPS() {}
    
            /** Initialization constructor. */
            FLensDistortionUVGenerationPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
                : FLensDistortionUVGenerationShader(Initializer)
            { }
        };
    
        IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationVS, TEXT("/Plugin/Foo/Private/MyShader.usf"), TEXT("MainVS"), SF_Vertex)
        IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationPS, TEXT("/Plugin/Foo/Private/MyShader.usf"), TEXT("MainPS"), SF_Pixel)
    
        static void DrawUVDisplacementToRenderTarget_RenderThread(
            FRHICommandListImmediate& RHICmdList,
            const FCompiledCameraModel& CompiledCameraModel,
            const FName& TextureRenderTargetName,
            FTextureRenderTargetResource* OutTextureRenderTargetResource,
            ERHIFeatureLevel::Type FeatureLevel)
        {
            check(IsInRenderingThread());
    
        #if WANTS_DRAW_MESH_EVENTS
            FString EventName;
            TextureRenderTargetName.ToString(EventName);
            SCOPED_DRAW_EVENTF(RHICmdList, SceneCapture, TEXT("LensDistortionDisplacementGeneration %s"), *EventName);
        #else
            SCOPED_DRAW_EVENT(RHICmdList, DrawUVDisplacementToRenderTarget_RenderThread);
        #endif
    
            // Set render target.
            SetRenderTarget(
                RHICmdList,
                OutTextureRenderTargetResource->GetRenderTargetTexture(),
                FTextureRHIRef(),
                ESimpleRenderTargetMode::EUninitializedColorAndDepth,
                FExclusiveDepthStencil::DepthNop_StencilNop);
    
            FIntPoint DisplacementMapResolution(OutTextureRenderTargetResource->GetSizeX(), OutTextureRenderTargetResource->GetSizeY());
    
            // Update viewport.
            RHICmdList.SetViewport(
                0, 0, 0.f,
                DisplacementMapResolution.X, DisplacementMapResolution.Y, 1.f);
    
            // Get shaders.
            TShaderMap<FGlobalShaderType>* GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
            TShaderMapRef< FLensDistortionUVGenerationVS > VertexShader(GlobalShaderMap);
            TShaderMapRef< FLensDistortionUVGenerationPS > PixelShader(GlobalShaderMap);
    
            // Set the graphic pipeline state.
            FGraphicsPipelineStateInitializer GraphicsPSOInit;
            RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
            GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
            GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
            GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
            GraphicsPSOInit.PrimitiveType = PT_TriangleList;
            GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
            GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
            GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
            SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
    
            // Update viewport.
            RHICmdList.SetViewport(
                0, 0, 0.f,
                OutTextureRenderTargetResource->GetSizeX(), OutTextureRenderTargetResource->GetSizeY(), 1.f);
    
            // Update shader uniform parameters.
            VertexShader->SetParameters(RHICmdList, VertexShader->GetVertexShader(), CompiledCameraModel, DisplacementMapResolution);
            PixelShader->SetParameters(RHICmdList, PixelShader->GetPixelShader(), CompiledCameraModel, DisplacementMapResolution);
    
            // Draw grid.
            uint32 PrimitiveCount = kGridSubdivisionX * kGridSubdivisionY * 2;
            RHICmdList.DrawPrimitive(PT_TriangleList, 0, PrimitiveCount, 1);
    
            // Resolve render target.
            RHICmdList.CopyToResolveTarget(
                OutTextureRenderTargetResource->GetRenderTargetTexture(),
                OutTextureRenderTargetResource->TextureRHI,
                false, FResolveParams());
        }
    
        FVector2D FFooCameraModel::UndistortNormalizedViewPosition(FVector2D EngineV) const
        {
            // Engine view space -> standard view space.
            FVector2D V = FVector2D(1, -1) * EngineV;
    
            FVector2D V2 = V * V;
            float R2 = V2.X + V2.Y;
    
            // Radial distortion (extra parenthesis to match MF_Undistortion.uasset).
            FVector2D UndistortedV = V * (1.0 + (R2 * K1 + (R2 * R2) * K2 + (R2 * R2 * R2) * K3));
    
            // Tangential distortion.
            UndistortedV.X += P2 * (R2 + 2 * V2.X) + 2 * P1 * V.X * V.Y;
            UndistortedV.Y += P1 * (R2 + 2 * V2.Y) + 2 * P2 * V.X * V.Y;
    
            // Returns engine V.
            return UndistortedV * FVector2D(1, -1);
        }
    
        /** Compiles the camera model. */
        float FFooCameraModel::GetUndistortOverscanFactor(
            float DistortedHorizontalFOV, float DistortedAspectRatio) const
        {
            // If the lens distortion model is identity, then early return 1.
            if (*this == FFooCameraModel())
            {
                return 1.0f;
            }
    
            float TanHalfDistortedHorizontalFOV = FMath::Tan(DistortedHorizontalFOV * 0.5f);
    
            // Get the position in the view space at z'=1 of different key point in the distorted Viewport UV coordinate system.
            // This very approximative to know the required overscan scale factor of the undistorted viewport, but works really well in practice.
            //
            //  Undistorted UV position in the view space:
            //                 ^ View space's Y
            //                 |
            //        0        1        2
            //     
            //        7        0        3 --> View space's X
            //     
            //        6        5        4
            FVector2D UndistortCornerPos0 = LensUndistortViewportUVIntoViewSpace(
                *this, TanHalfDistortedHorizontalFOV, DistortedAspectRatio, FVector2D(0.0f, 0.0f));
            FVector2D UndistortCornerPos1 = LensUndistortViewportUVIntoViewSpace(
                *this, TanHalfDistortedHorizontalFOV, DistortedAspectRatio, FVector2D(0.5f, 0.0f));
            FVector2D UndistortCornerPos2 = LensUndistortViewportUVIntoViewSpace(
                *this, TanHalfDistortedHorizontalFOV, DistortedAspectRatio, FVector2D(1.0f, 0.0f));
            FVector2D UndistortCornerPos3 = LensUndistortViewportUVIntoViewSpace(
                *this, TanHalfDistortedHorizontalFOV, DistortedAspectRatio, FVector2D(1.0f, 0.5f));
            FVector2D UndistortCornerPos4 = LensUndistortViewportUVIntoViewSpace(
                *this, TanHalfDistortedHorizontalFOV, DistortedAspectRatio, FVector2D(1.0f, 1.0f));
            FVector2D UndistortCornerPos5 = LensUndistortViewportUVIntoViewSpace(
                *this, TanHalfDistortedHorizontalFOV, DistortedAspectRatio, FVector2D(0.5f, 1.0f));
            FVector2D UndistortCornerPos6 = LensUndistortViewportUVIntoViewSpace(
                *this, TanHalfDistortedHorizontalFOV, DistortedAspectRatio, FVector2D(0.0f, 1.0f));
            FVector2D UndistortCornerPos7 = LensUndistortViewportUVIntoViewSpace(
                *this, TanHalfDistortedHorizontalFOV, DistortedAspectRatio, FVector2D(0.0f, 0.5f));
    
            // Find min and max of the inner square of undistorted Viewport in the view space at z'=1.
            FVector2D MinInnerViewportRect;
            FVector2D MaxInnerViewportRect;
            MinInnerViewportRect.X = FMath::Max3(UndistortCornerPos0.X, UndistortCornerPos6.X, UndistortCornerPos7.X);
            MinInnerViewportRect.Y = FMath::Max3(UndistortCornerPos4.Y, UndistortCornerPos5.Y, UndistortCornerPos6.Y);
            MaxInnerViewportRect.X = FMath::Min3(UndistortCornerPos2.X, UndistortCornerPos3.X, UndistortCornerPos4.X);
            MaxInnerViewportRect.Y = FMath::Min3(UndistortCornerPos0.Y, UndistortCornerPos1.Y, UndistortCornerPos2.Y);
    
            check(MinInnerViewportRect.X < 0.f);
            check(MinInnerViewportRect.Y < 0.f);
            check(MaxInnerViewportRect.X > 0.f);
            check(MaxInnerViewportRect.Y > 0.f);
    
            // Compute tan(VerticalFOV * 0.5)
            float TanHalfDistortedVerticalFOV = TanHalfDistortedHorizontalFOV / DistortedAspectRatio;
    
            // Compute the required un-distorted viewport scale on each axis.
            FVector2D ViewportScaleUpFactorPerViewAxis = 0.5 * FVector2D(
                TanHalfDistortedHorizontalFOV / FMath::Max(-MinInnerViewportRect.X, MaxInnerViewportRect.X),
                TanHalfDistortedVerticalFOV / FMath::Max(-MinInnerViewportRect.Y, MaxInnerViewportRect.Y));
    
            // Scale up by 2% more the undistorted viewport size in the view space to work
            // around the fact that odd undistorted positions might not exactly be at the minimal
            // in case of a tangential distorted barrel lens distortion.
            const float ViewportScaleUpConstMultiplier = 1.02f;
            return FMath::Max(ViewportScaleUpFactorPerViewAxis.X, ViewportScaleUpFactorPerViewAxis.Y) * ViewportScaleUpConstMultiplier;
        }
    
        void FFooCameraModel::DrawUVDisplacementToRenderTarget(
            UWorld* World,
            float DistortedHorizontalFOV,
            float DistortedAspectRatio,
            float UndistortOverscanFactor,
            UTextureRenderTarget2D* OutputRenderTarget,
            float OutputMultiply,
            float OutputAdd) const
        {
            check(IsInGameThread());
    
            // Compiles the camera model to know the overscan scale factor.
            float TanHalfUndistortedHorizontalFOV = FMath::Tan(DistortedHorizontalFOV * 0.5f) * UndistortOverscanFactor;
            float TanHalfUndistortedVerticalFOV = TanHalfUndistortedHorizontalFOV / DistortedAspectRatio;
    
            // Output.
            FCompiledCameraModel CompiledCameraModel;
            CompiledCameraModel.OriginalCameraModel = *this;
    
            CompiledCameraModel.DistortedCameraMatrix.X = 1.0f / TanHalfUndistortedHorizontalFOV;
            CompiledCameraModel.DistortedCameraMatrix.Y = 1.0f / TanHalfUndistortedVerticalFOV;
            CompiledCameraModel.DistortedCameraMatrix.Z = 0.5f;
            CompiledCameraModel.DistortedCameraMatrix.W = 0.5f;
    
            CompiledCameraModel.UndistortedCameraMatrix.X = F.X;
            CompiledCameraModel.UndistortedCameraMatrix.Y = F.Y * DistortedAspectRatio;
            CompiledCameraModel.UndistortedCameraMatrix.Z = C.X;
            CompiledCameraModel.UndistortedCameraMatrix.W = C.Y;
    
            CompiledCameraModel.OutputMultiplyAndAdd.X = OutputMultiply;
            CompiledCameraModel.OutputMultiplyAndAdd.Y = OutputAdd;
    
            const FName TextureRenderTargetName = OutputRenderTarget->GetFName();
            FTextureRenderTargetResource* TextureRenderTargetResource = OutputRenderTarget->GameThread_GetRenderTargetResource();
    
            ERHIFeatureLevel::Type FeatureLevel = World->Scene->GetFeatureLevel();
    
            ENQUEUE_RENDER_COMMAND(CaptureCommand)(
                [CompiledCameraModel, TextureRenderTargetResource, TextureRenderTargetName, FeatureLevel](FRHICommandListImmediate& RHICmdList)
                {
                    DrawUVDisplacementToRenderTarget_RenderThread(
                        RHICmdList,
                        CompiledCameraModel,
                        TextureRenderTargetName,
                        TextureRenderTargetResource,
                        FeatureLevel);
                }
            );
        }
  17. 마지막으로, LensDistortionRendering.cpp 파일 155-156 번 다음 두 줄을 앞서 생성한 MyShader.USF 파일을 가리키도록 변경합니다.

    대체 원본:

    • IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationVS, TEXT("/Plugin/LensDistortion/Private/UVGeneration.usf"), TEXT("MainVS"), SF_Vertex)

    대체 대상:

    • IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationVS, TEXT("/Plugin/Foo/Private/MyShader.usf"), TEXT("MainVS"), SF_Vertex)

    대체 원본:

    • IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationPS, TEXT("/Plugin/LensDistortion/Private/UVGeneration.usf"), TEXT("MainPS"), SF_Pixel)

    대체 대상:

    • IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationPS, TEXT("/Plugin/Foo/Private/MyShader.usf"), TEXT("MainPS"), SF_Pixel)

  18. Foo/Source 폴더로 이동하여 Foo.Build.cs 파일을 열고, Foo.Build.cs 파일의 PublicDependencyModuleNames.AddRange 섹션 아래 다음 코드 줄을 추가합니다:

        PublicDependencyModuleNames.AddRange(
        new string[]
        {
            "Core",
            "RenderCore",
            "ShaderCore",
            "RHI",
            // ... add other public dependencies that you statically link with here ...
        }
        );
  19. Foo.Build.cs 파일의 PrivateDependencyModuleNames.AddRange 섹션에서 SlateSlateCore 를 제거합니다. 완료된 Foo.Build.cs 파일은 다음과 같습니다:

    // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
    
    using UnrealBuildTool;
    
    public class Foo : ModuleRules
    {
        public Foo(ReadOnlyTargetRules Target) : base(Target)
        {
            PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
    
            PublicIncludePaths.AddRange(
                new string[] {
                    "Foo/Public"
                    // ... add public include paths required here ...
                }
                );    
            PrivateIncludePaths.AddRange(
                new string[] {
                    "Foo/Private",
                    // ... add other private include paths required here ...
                }
                );
    
            PublicDependencyModuleNames.AddRange(
                new string[]
                {
                    "Core",
                    "RenderCore",
                    "ShaderCore",
                    "RHI",
                    // ... add other public dependencies that you statically link with here ...
                }
                );
            PrivateDependencyModuleNames.AddRange(
                new string[]
                {
                    "CoreUObject",
                    "Engine",
                    // ... add private dependencies that you statically link with here ...    
                }
                );
    
            DynamicallyLoadedModuleNames.AddRange(
                new string[]
                {
                    // ... add any modules that your module loads dynamically here ...
                }
                );
        }
    }
  20. 프로젝트의 Visual Studio 솔루션 파일을 다시 실행한 뒤 Ctrl + 5 키를 눌러 프로젝트를 다시 컴파일합니다. 완료되면 F5 키를 눌러 UE4 에디터를 실행합니다.

  21. UE4 에디터 로드가 완료되면, 편집 > Plugins 에서 Plugins 매니저로 이동합니다.

    HT_ShadersInPlugins_08.png

  22. 플러그인 매니저 화면을 아래로 내려보면 Project 섹션 아래 새로 만든 플러그인을 찾을 수 있을 것입니다.

    HT_ShadersInPlugins_09.png

    플러그인이 활성화되지 않은 경우, 그 이름 옆의 박스를 체크하고 UE4 에디터를 재시작합니다.

  23. 모든 것이 제대로 되어있는지 확인하기 위해서는, 레벨 블루프린트를 열고 이벤트 그래프에 우클릭한 뒤 검색창에 Foo 라 입력합니다. 그러면 Foo Camera 카테고리에 추가된 모든 항목이 보일 것입니다.

    클릭하면 이미지 원본을 확인합니다.

최종 결과

축하합니다, 렌즈 디스토션 UE4 플러그인을 새로 만들어 컴파일하는 데 성공하셨습니다. 다음 단계에서는 블루프린트에서 이 새로운 글로벌 셰이더를 불러오는 법과 렌더 타깃을 왜곡시키는 법을 살펴보겠습니다.

Select Skin
Light
Dark

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

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

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

네이버 카페
공식 포럼