ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UE4]Global Shader
    UE4 & UE5/Rendering 2020. 3. 17. 08:24

    아래 사이트의 Shader 파트를 참고하여 작성하였으며, 언리얼엔진 4.24.1에서 동작 가능하게 코드를 수정하였습니다.

    출처 : https://zhuanlan.zhihu.com/p/36695496

     

    환경

    Unreal Engine 4.24.1

     

    목표

    1. Plugin 형태로 제공되는 간단한 GlobalShader를 제작합니다.

    2. 자신만의 Vertex Struct를 구성하고 VertexBuffer를 만듭니다.

    3. Shader에서 변수를 사용해봅니다.

    4. Shader에서 Uniform 변수를 사용해봅니다.

     

    Plugin을 아래 이미지에서 Blank로 생성

    플러그인 폴더의 [PluginName.uplugin] 파일내에 아래 부분을 고쳐줌.

     

    [PluginName.Build.cs] 를 아래와 같이 수정. PublicDependencyModuleNames에 RenderCore와 Projects를 추가함

    using UnrealBuildTool;
    
    public class GlobalShaderTest : ModuleRules
    {
    	public GlobalShaderTest(ReadOnlyTargetRules Target) : base(Target)
    	{
    		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
    		
    		PublicIncludePaths.AddRange(
    			new string[] {
    				// ... add public include paths required here ...
    			}
    			);
    				
    		
    		PrivateIncludePaths.AddRange(
    			new string[] {
    				// ... add other private include paths required here ...
    			}
    			);
    			
    		
    		PublicDependencyModuleNames.AddRange(
    			new string[]
    			{
    				"Core",
    				// ... add other public dependencies that you statically link with here ...
    				"CoreUObject",
    				"Engine",
    				"RHI",
    				"RenderCore",
    				"Projects"
    			}
    			);
    			
    		
    		PrivateDependencyModuleNames.AddRange(
    			new string[]
    			{
    				"CoreUObject",
    				"Engine",
    				"Slate",
    				"SlateCore",
    				// ... add private dependencies that you statically link with here ...	
    			}
    			);
    		
    		
    		DynamicallyLoadedModuleNames.AddRange(
    			new string[]
    			{
    				// ... add any modules that your module loads dynamically here ...
    			}
    			);
    	}
    }
    

     

    Plugin 폴더에 있는 usf 파일을 감지할 수 있게 아래와 같이 Plugin Module 시작지점에 Shader 폴더 추가

    // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
    
    #include "GlobalShaderTest.h"
    #include "IPluginManager.h"
    #include "Paths.h"
    #include "ShaderCore.h"
    
    #define LOCTEXT_NAMESPACE "FGlobalShaderTestModule"
    
    void FGlobalShaderTestModule::StartupModule()
    {
    	auto Plugin = IPluginManager::Get().FindPlugin(TEXT("GlobalShaderTest"));
    	FString PluginShaderDir = FPaths::Combine(Plugin->GetBaseDir(), TEXT("Shaders"));
    	AddShaderSourceDirectoryMapping(TEXT("/Plugin/GlobalShaderTest"), PluginShaderDir);
    }
    
    void FGlobalShaderTestModule::ShutdownModule()
    {
    	// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
    	// we call this function before unloading the module.
    }
    
    #undef LOCTEXT_NAMESPACE
    	
    IMPLEMENT_MODULE(FGlobalShaderTestModule, GlobalShaderTest)

     

    Vertex Structure

    struct FMyTextureVertex
    {
    	FVector4 Position;
    	FVector2D UV;
    };

     

    Vertex Declaration

    class FMyTextureVertexDeclaration : public FRenderResource
    {
    public:
    	FVertexDeclarationRHIRef VertexDeclarationRHI;
    
    	virtual void InitRHI() override
    	{
    		FVertexDeclarationElementList Elements;
    		uint32 Stride = sizeof(FMyTextureVertex);
    		Elements.Add(FVertexElement(0, STRUCT_OFFSET(FMyTextureVertex, Position), VET_Float4, 0, Stride));
    		Elements.Add(FVertexElement(0, STRUCT_OFFSET(FMyTextureVertex, UV), VET_Float2, 1, Stride));
    		VertexDeclarationRHI = RHICreateVertexDeclaration(Elements);
    	}
    
    	virtual void ReleaseRHI() override
    	{
    		VertexDeclarationRHI->Release();
    	}
    };

     

    VertexBuffer

    class FMyVertexBuffer : public FVertexBuffer
    {
    public:
    	/**
    	* Initialize the RHI for this rendering resource
    	*/
    	virtual void InitRHI() override
    	{
    		// create a static vertex buffer
    		FRHIResourceCreateInfo CreateInfo;
    		VertexBufferRHI = RHICreateVertexBuffer(sizeof(FMyTextureVertex) * 4, BUF_Static, CreateInfo);
    		void* VoidPtr = RHILockVertexBuffer(VertexBufferRHI, 0, sizeof(FMyTextureVertex) * 4, RLM_WriteOnly);
    		// Generate the vertices used
    		FMyTextureVertex* Vertices = reinterpret_cast<FMyTextureVertex*>(VoidPtr);
    
    		Vertices[0].Position = FVector4(-1.0f, 1.0f, 0.0f, 1.0f);
    		Vertices[1].Position = FVector4(1.0f, 1.0f, 0.0f, 1.0f);
    		Vertices[2].Position = FVector4(-1.0f, -1.0f, 0.0f, 1.0f);
    		Vertices[3].Position = FVector4(1.0f, -1.0f, 0.0f, 1.0f);
    		Vertices[0].UV = FVector2D(0.0f, 1.0f);
    		Vertices[1].UV = FVector2D(1.0f, 1.0f);
    		Vertices[2].UV = FVector2D(0.0f, 0.0f);
    		Vertices[3].UV = FVector2D(1.0f, 0.0f);
    		RHIUnlockVertexBuffer(VertexBufferRHI);
    	}
    };

     

    UniformBuffer

    BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FGlobalUniformStructData,)
    		SHADER_PARAMETER(FVector4, ColorOne)
    		SHADER_PARAMETER(FVector4, ColorTwo)
    		SHADER_PARAMETER(FVector4, ColorThree)
    		SHADER_PARAMETER(FVector4, ColorFour)
    		SHADER_PARAMETER(uint32, ColorIndex)
    END_GLOBAL_SHADER_PARAMETER_STRUCT()
    
    IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FGlobalUniformStructData, "FGlobalShaderTestUniform");

     

    usf 파일 추가 (Shader)

    // /Plugins/GlobalShaderTest/Shaders/Private/GlobalShaderTest.usf
    
    #include "/Engine/Public/Platform.ush"
    #include "/Engine/Private/Common.ush"           // C++에서 만든 UniformBuffer 사용위해 추가
    
    float4 SimpleColor;
    Texture2D MyTexture;
    SamplerState MyTextureSampler;
    
    void MainVS(
    	in float4 InPosition : ATTRIBUTE0,
    	in float2 InUV : ATTRIBUTE1,
    	out float2 OutUV : TEXCOORD0,
    	out float4 OutPosition : SV_POSITION
    	)
    {
    	OutPosition = InPosition;
        OutUV = InUV;
    }
    
    void MainPS(
    	in float2 UV : TEXCOORD0,
    	out float4 OutColor : SV_Target0
    	)
    {
        OutColor = float4(MyTexture.Sample(MyTextureSampler, UV.xy).rgb, 1.0f);
        OutColor *= SimpleColor;
    	
        switch (FGlobalShaderTestUniform.ColorIndex)
        {
    		case 0:
                OutColor *= FGlobalShaderTestUniform.ColorOne;
                break;
    		case 1:
                OutColor *= FGlobalShaderTestUniform.ColorTwo;
                break;
    		case 2:
                OutColor *= FGlobalShaderTestUniform.ColorThree;
                break;
    		case 3:
                OutColor *= FGlobalShaderTestUniform.ColorFour;
                break;
        }
    
    }

     

    VertexShader and PixelShader

    class FGlobalShaderTest : public FGlobalShader
    {
    public:
    	FGlobalShaderTest() {}
    	FGlobalShaderTest(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
    		: FGlobalShader(Initializer)
    	{
    		SimpleColorVal.Bind(Initializer.ParameterMap, TEXT("SimpleColor"));
    		TestTextureVal.Bind(Initializer.ParameterMap, TEXT("MyTexture"));
    		TestTextureSampler.Bind(Initializer.ParameterMap, TEXT("MyTextureSampler"));
    	}
    
    	static bool ShouldCache(EShaderPlatform Platform)
    	{
    		return true;
    	}
    
    	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    	{
    		return true;
    	}
    
    	static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    	{
    		FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
    		OutEnvironment.SetDefine(TEXT("TEST_MICRO"), 1);
    	}
    	
    	void SetParameters(FRHICommandListImmediate& RHICmdList, const FLinearColor& MyColor
    		, FTextureReferenceRHIRef MyTextureRHI, FGlobalShaderStructData& InShaderStructData)
    	{
    		SetShaderValue(RHICmdList, GetPixelShader(), SimpleColorVal, MyColor);
    		SetTextureParameter(RHICmdList, GetPixelShader(), TestTextureVal, TestTextureSampler
    			, TStaticSamplerState<SF_Trilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(), MyTextureRHI);
    
    		FGlobalUniformStructData UniformData;
    		UniformData.ColorOne = InShaderStructData.ColorOne;
    		UniformData.ColorTwo = InShaderStructData.ColorTwo;
    		UniformData.ColorThree = InShaderStructData.ColorThree;
    		UniformData.ColorFour = InShaderStructData.ColorFour;
    		UniformData.ColorIndex = InShaderStructData.ColorIndex;
    		SetUniformBufferParameterImmediate(RHICmdList, GetPixelShader(), GetUniformBufferParameter<FGlobalUniformStructData>(), UniformData);
    	}
    
    	virtual bool Serialize(FArchive& Ar) override
    	{
    		bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
    		Ar << SimpleColorVal << TestTextureVal;
    		return bShaderHasOutdatedParameters;
    	}
    
    private:
    	FShaderParameter SimpleColorVal;
    
    	FShaderResourceParameter TestTextureVal;
    	FShaderResourceParameter TestTextureSampler;
    };
    
    class FGlobalShaderTestVS : public FGlobalShaderTest
    {
    	DECLARE_SHADER_TYPE(FGlobalShaderTestVS, Global);
    
    public:
    	FGlobalShaderTestVS() {}
    
    	FGlobalShaderTestVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
    		: FGlobalShaderTest(Initializer)
    	{
    
    	}
    };
    
    class FGlobalShaderTestPS : public FGlobalShaderTest
    {
    	DECLARE_SHADER_TYPE(FGlobalShaderTestPS, Global);
    
    public:
    	FGlobalShaderTestPS() {}
    
    	FGlobalShaderTestPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
    		: FGlobalShaderTest(Initializer)
    	{
    
    	}
    };
    
    IMPLEMENT_SHADER_TYPE(, FGlobalShaderTestVS, TEXT("/Plugin/GlobalShaderTest/Private/GlobalShaderTest.usf"), TEXT("MainVS"), SF_Vertex)
    IMPLEMENT_SHADER_TYPE(, FGlobalShaderTestPS, TEXT("/Plugin/GlobalShaderTest/Private/GlobalShaderTest.usf"), TEXT("MainPS"), SF_Pixel)

     

    대상 렌더타겟에 렌더링 해주는 코드

    extern TGlobalResource<FMyVertexBuffer> GMyVertexBuffer;
    
    // Blueprint에서 UniformBuffer의 데이터를 입력받아올 구조체
    USTRUCT(BlueprintType)
    struct FGlobalShaderStructData
    {
    	GENERATED_USTRUCT_BODY()
    
    	UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = ShaderData)
    	FLinearColor ColorOne;
    
    	UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = ShaderData)
    	FLinearColor ColorTwo;
    
    	UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = ShaderData)
    	FLinearColor ColorThree;
    
    	UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = ShaderData)
    	FLinearColor ColorFour;
    
    	UPROPERTY(BlueprintReadWrite, VisibleAnywhere, Category = ShaderData)
    	int32 ColorIndex;
    };
    
    // Blueprint에 노출시킬 함수 선언
    UCLASS(MinimalAPI, meta = (ScriptName = "TestShaderLibrary"))
    class UGlobalTestShaderBlueprintLibrary : public UBlueprintFunctionLibrary
    {
    	GENERATED_UCLASS_BODY()
    
    	UFUNCTION(BlueprintCallable, Category = "GlobalShaderTestPlugin", meta = (WorldContext = "WorldContextObject"))
    	static void DrawGlobalTestShaderRenderTarget(class UTextureRenderTarget2D* OutputRenderTarget
    		, AActor* Actor, FLinearColor MyColor, UTexture* MyTexture, FGlobalShaderStructData ShaderStructData);
    };
    

     

    // RenderTarget에 렌더링하는 코드
    
    TGlobalResource<FMyVertexBuffer> GMyVertexBuffer;
    
    static void DrawTestShaderRenderTarget_RenderThread(
    	FRHICommandListImmediate& RHICmdList,
    	FTextureRenderTargetResource* OutputRenderTargetResource,
    	ERHIFeatureLevel::Type FeatureLevel,
    	FName TextureRenderTargetName,
    	FLinearColor MyColor,
    	FTextureReferenceRHIRef MyTextureRHI,
    	FGlobalShaderStructData ShaderStructData
    )
    {
    	check(IsInRenderingThread());
    
    #if WANTS_DRAW_MESH_EVENTS
    	FString EventName;
    	TextureRenderTargetName.ToString(EventName);
    	SCOPED_DRAW_EVENTF(RHICmdList, SceneCapture, TEXT("GlobalSahderTest %s"), *EventName);
    #else
    	SCOPED_DRAW_EVENT(RHICmdList, DrawTestShaderRenderTarget_RenderThread);
    #endif
    
    	FRHIRenderPassInfo RPInfo(OutputRenderTargetResource->GetRenderTargetTexture(), ERenderTargetActions::Clear_Store, OutputRenderTargetResource->TextureRHI, FExclusiveDepthStencil::DepthNop_StencilNop);
    	RHICmdList.BeginRenderPass(RPInfo, TEXT("DrawTestShaderRenderTarget_RenderThread"));
    	{
    		TShaderMap<FGlobalShaderType>* GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
    		TShaderMapRef<FGlobalShaderTestVS> VertexShader(GlobalShaderMap);
    		TShaderMapRef<FGlobalShaderTestPS> PixelShader(GlobalShaderMap);
    
    		FMyTextureVertexDeclaration VertexDec;
    		VertexDec.InitRHI();
    
    		// 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_TriangleStrip;
    		GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = VertexDec.VertexDeclarationRHI;
    		GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
    		GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
    		SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
    
    		PixelShader->SetParameters(RHICmdList, MyColor, MyTextureRHI, ShaderStructData);
    
    		RHICmdList.SetStreamSource(0, GMyVertexBuffer.VertexBufferRHI, 0);
    		RHICmdList.DrawPrimitive(0, 2, 1);
    
    		// Resolve render target.  
    		RHICmdList.CopyToResolveTarget(
    			OutputRenderTargetResource->GetRenderTargetTexture(),
    			OutputRenderTargetResource->TextureRHI,
    			FResolveParams());
    	}
    	RHICmdList.EndRenderPass();
    }
    
    void UGlobalTestShaderBlueprintLibrary::DrawGlobalTestShaderRenderTarget(class UTextureRenderTarget2D* OutputRenderTarget
    	, AActor* Actor, FLinearColor MyColor, UTexture* MyTexture, FGlobalShaderStructData ShaderStructData)
    {
    	check(IsInGameThread());
    
    	if (!OutputRenderTarget)
    		return;
    	
    	if (!Actor)
    		return;
    
    	FTextureRenderTargetResource* TextureRenderTargetResource = OutputRenderTarget->GameThread_GetRenderTargetResource();
    	FTextureReferenceRHIRef MyTextureRHI = MyTexture->TextureReference.TextureReferenceRHI;
    	UWorld* World = Actor->GetWorld();
    	ERHIFeatureLevel::Type FeatureLevel = World->Scene->GetFeatureLevel();
    	FName TextureRenderTargetName = OutputRenderTarget->GetFName();
    	ENQUEUE_RENDER_COMMAND(CaptureCommand)(
    		[TextureRenderTargetResource, FeatureLevel, MyColor, TextureRenderTargetName, MyTextureRHI, ShaderStructData]
    		(FRHICommandListImmediate& RHICmdList)
    		{
    			DrawTestShaderRenderTarget_RenderThread(RHICmdList, TextureRenderTargetResource
    				, FeatureLevel, TextureRenderTargetName, MyColor, MyTextureRHI, ShaderStructData);
    		}
    	);
    }

     

    아래와 같은 3가지 에셋 생성

     

    Material은 단순히 RenderTarget의 텍스쳐를 BaseColor에 연결해줌

    Blueprint Actor에 아래와 같이 설정

    DrawGlobalTestShaderRenderTarget 노드에 결과를 그릴 RenderTarget과 UniformBuffer에 사용할 변수값들을 설정해준다.

    1, 2, 3, 4 버튼 누를 시에 컬러 변경됨

    변경시킨 렌더타겟을 이 블루프린트 액터에서 사용하기

     

     

    결과

    1, 2, 3, 4 버튼을 차례대로 누르면 아래와 같이 색상이 변화하는 것을 확인할 수 있다.

    'UE4 & UE5 > Rendering' 카테고리의 다른 글

    [UE5] Shader [1/3] - 주요 클래스 파악  (8) 2021.09.03
    [UE5] MeshDrawCommand (2/2)  (3) 2021.06.20
    [UE5] MeshDrawCommand (1/2)  (0) 2021.06.20
    [UE4]Geometry Shader  (0) 2020.04.05
    [UE4]Compute Shader  (2) 2020.03.20

    댓글

Designed by Tistory & scahp.