ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UE4]Compute Shader
    UE4 & UE5/Rendering 2020. 3. 20. 09:07

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

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

     

    환경

    Unreal Engine 4.24.1

    [UE4]Global Shader 파트에 계속해서 내용 추가

     

    목표

    1. Compute Shader로 Texture에 이미지를 그립니다.

    2. 이미지를 그린 Texture를 렌더타겟에다 그려서 사용합니다.

    3. StructuredBuffer를 사용해 봅니다.

     

    C++에서 ComputeShader 생성

    class FGlobalComputeTestShader : public FGlobalShader
    {
    	DECLARE_SHADER_TYPE(FGlobalComputeTestShader, Global);
    
    public:
    	FGlobalComputeTestShader() {}
    	FGlobalComputeTestShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
    		: FGlobalShader(Initializer)
    	{
    		OutputSurface.Bind(Initializer.ParameterMap, TEXT("OutputSurface"));
    		TestStructureBufferSurface.Bind(Initializer.ParameterMap, TEXT("TestStructureBuffer"));
    	}
    
    	static bool ShouldCache(EShaderPlatform Platform)
    	{
    		return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5);
    	}
    
    	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
    	{
    		return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5);
    	}
    
    	virtual bool Serialize(FArchive& Ar) override
    	{
    		bool bShaderHasOutdatedParam = FGlobalShader::Serialize(Ar);
    		Ar << OutputSurface << TestStructureBufferSurface;
    		return bShaderHasOutdatedParam;
    	}
    
    	void SetSurface(FRHICommandList& RHICmdList, FUnorderedAccessViewRHIRef OutputSurfaceUAV
    		, FGlobalShaderStructData& ShaderStructData, FUnorderedAccessViewRHIRef& TestStructureBuffUAV)
    	{
    		// RenderTargetTexture
    		FComputeShaderRHIRef ComputeShaderRHI = GetComputeShader();
    		if (OutputSurface.IsBound())
    			RHICmdList.SetUAVParameter(ComputeShaderRHI, OutputSurface.GetBaseIndex(), OutputSurfaceUAV);
    
    		// UniformBuffer
    		FGlobalUniformStructData UniformData;
    		UniformData.ColorOne = ShaderStructData.ColorOne;
    		UniformData.ColorTwo = ShaderStructData.ColorTwo;
    		UniformData.ColorThree = ShaderStructData.ColorThree;
    		UniformData.ColorFour = ShaderStructData.ColorFour;
    		UniformData.ColorIndex = ShaderStructData.ColorIndex;
    		SetUniformBufferParameterImmediate(RHICmdList, GetComputeShader(), GetUniformBufferParameter<FGlobalUniformStructData>(), UniformData);
    
    		// StructuredBuffer
    		if (TestStructureBufferSurface.IsBound())
    			RHICmdList.SetUAVParameter(ComputeShaderRHI, TestStructureBufferSurface.GetUAVIndex(), TestStructureBuffUAV);
    	}
    
    	void UnbindBuffers(FRHICommandList& RHICmdList)
    	{
    		FComputeShaderRHIRef ComputeShaderRHI = GetComputeShader();
    		if (OutputSurface.IsBound())
    			RHICmdList.SetUAVParameter(ComputeShaderRHI, OutputSurface.GetBaseIndex(), FUnorderedAccessViewRHIRef());
    
    		if (TestStructureBufferSurface.IsBound())
    			RHICmdList.SetUAVParameter(ComputeShaderRHI, TestStructureBufferSurface.GetUAVIndex(), FUnorderedAccessViewRHIRef());
    	}
    
    private:
    	FShaderResourceParameter OutputSurface;
    	FRWShaderParameter TestStructureBufferSurface;
    };
    
    IMPLEMENT_SHADER_TYPE(, FGlobalComputeTestShader, TEXT("/Plugin/globalShaderTest/Private/GlobalShaderTest.usf"), TEXT("MainCS"), SF_Compute);

     

    Compute Shader .usf 작성

    // GlobalShaderTest.usf
    
    #include "/Engine/Public/Platform.ush"
    #include "/Engine/Private/Common.ush"      // C++에서 만든 UniformBuffer 사용위해 추가
                                               // C++에서 정의한 모든 UniformSturct는 Common에 있음
    
    struct TestStruct
    {
        float3 TestPosition;
    };
    
    RWStructuredBuffer<TestStruct> TestStructureBuffer;
    RWTexture2D<float4> OutputSurface;
    
    [numthreads(32, 32, 1)]
    void MainCS(uint3 ThreadID : SV_DispatchThreadID)
    {
        float SizeX, SizeY;
        OutputSurface.GetDimensions(SizeX, SizeY);
        
        float2 Resolution = float2(SizeX, SizeY);
        float2 UV = (ThreadID.xy / Resolution.xy) - 0.5f;
        
        // FGlobalShaderTestUniform 라는 UniformBuffer의 ColorOne.r를 시간으로 사용.
        float GlobalTime = FGlobalShaderTestUniform.ColorOne.r;
        
        float T = GlobalTime * 0.1 + ((0.25 + 0.05 * sin(GlobalTime * 0.1)) / (length(UV.xy) + 0.07)) * 2.2;
        float SI = sin(T);
        float CO = cos(T);
        float2x2 MA = { CO, SI, -SI, CO };
        
        float V1, V2, V3;
        V1 = V2 = V3 = 0.0;
        
        float S = 0.0;
        for (int i = 0; i < 90; i++)
        {
            float3 P = S * float3(UV, 0.0);
            P.xy = mul(P.xy, MA);
            P += float3(0.22, 0.3, S - 1.5 - sin(GlobalTime * 0.13) * 0.1);
              
            for (int i = 0; i < 8; i++)    
                P = abs(P) / dot(P, P) - 0.659;
      
            V1 += dot(P, P) * 0.0015 * (1.8 + sin(length(UV.xy * 13.0) + 0.5 - GlobalTime * 0.2));
            V2 += dot(P, P) * 0.0013 * (1.5 + sin(length(UV.xy * 14.5) + 1.2 - GlobalTime * 0.3));
            V3 += length(P.xy * 10.0) * 0.0003;
            S += 0.035;
        }
      
        float Len = length(UV);
        V1 *= lerp(0.7, 0.0, Len);
        V2 *= lerp(0.5, 0.0, Len);
        V3 *= lerp(0.9, 0.0, Len);
    
        float3 Col = float3(V3 * (1.5 + sin(GlobalTime * 0.2) * 0.4), (V1 + V3) * 0.3, V2)
                        + lerp(0.2, 0.0, Len) * 0.85
                        + lerp(0.0, 0.6, V3) * 0.3;
      
        float3 Powered = pow(abs(Col), float3(1.2, 1.2, 1.2));
        float3 Minimized = min(Powered, 1.0);
        float4 OutputColor = float4(Minimized, 1.0);
        
        OutputSurface[ThreadID.xy] = OutputColor;
        TestStructureBuffer[0].TestPosition = float3(0.5, 0.5, 0.5);    // 테스트로 임의의 값으로 변경 시켜봄
    }

     

    StructuredBuffer에 사용할 멤버를 관리할 구조체 정의

    struct FStructuredDataTest
    {
    	struct TestStruct
    	{
    		FVector TestPosition;
    	};
    
    	void Init()
    	{
    		if (IsInitied)
    			return;
    		IsInitied = true;
    
    		TestStruct TestElement;
    		TestElement.TestPosition = FVector(1.0f, 1.0f, 1.0f);
    		TResourceArray<TestStruct> BuffferData;
    		BuffferData.Reset();
    		BuffferData.Add(TestElement);
    		BuffferData.SetAllowCPUAccess(true);
    
    		FRHIResourceCreateInfo TestCreateInfo;
    		TestCreateInfo.ResourceArray = &BuffferData;
    
    		TestStructureBuff = RHICreateStructuredBuffer(sizeof(TestStruct)
    			, sizeof(TestStruct) * 1, BUF_UnorderedAccess | BUF_ShaderResource, TestCreateInfo);;
    		TestStructureBuffUAV = RHICreateUnorderedAccessView(TestStructureBuff, true, false);
    	}
    
    	FVector ReadStructuredBuffer(FVector InVector)
    	{
    		TArray<FVector> Data;
    		Data.Reset();
    		Data.Add(InVector);
    
    		FVector* SrcPtr = (FVector*)RHILockStructuredBuffer(TestStructureBuff.GetReference(), 0, sizeof(FVector), EResourceLockMode::RLM_ReadOnly);
    		FMemory::Memcpy(Data.GetData(), SrcPtr, sizeof(FVector));
    		RHIUnlockStructuredBuffer(TestStructureBuff.GetReference());
    		return Data[0];
    	}
    
    	bool IsInitied = false;
    	FStructuredBufferRHIRef TestStructureBuff;
    	FUnorderedAccessViewRHIRef TestStructureBuffUAV;
    };

     

     

    Compute Shader를 Dispatch 해주는 함수 작성

    static void UseComputeShader_RenderThread(FRHICommandListImmediate& RHICmdList, FTextureRenderTargetResource* OutputRenderTargetResource
    	, FGlobalShaderStructData ShaderStructData, ERHIFeatureLevel::Type FeatureLevel, FName TextureRenderTargetName)
    {
    	check(IsInRenderingThread());
    
    	TShaderMapRef<FGlobalComputeTestShader> ComputeShader(GetGlobalShaderMap(FeatureLevel));
    	RHICmdList.SetComputeShader(ComputeShader->GetComputeShader());
    
    	const int32 SizeX = OutputRenderTargetResource->GetSizeX();
    	const int32 SizeY = OutputRenderTargetResource->GetSizeY();
    	
    	FRHIResourceCreateInfo CreateInfo;
    
    	static FStructuredDataTest StructuredData;
    	StructuredData.Init();
    
    	// PF_A32B32G32R32F 이기 때문에 RenderTarget에서 RenderTargetFormat이 RTF_RGBA32f 와 같은 형태로 호환 가능해야 함
    	FTexture2DRHIRef Texture = RHICreateTexture2D(SizeX, SizeY, PF_A32B32G32R32F, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);
    	FUnorderedAccessViewRHIRef TextureUAV = RHICreateUnorderedAccessView(Texture);
    	ComputeShader->SetSurface(RHICmdList, TextureUAV, ShaderStructData, StructuredData.TestStructureBuffUAV);
    	DispatchComputeShader(RHICmdList, *ComputeShader, SizeX / 32, SizeY / 32, 1);
    	ComputeShader->UnbindBuffers(RHICmdList);
    	
    	FVector StructuredDataResult = StructuredData.ReadStructuredBuffer(FVector(1.0f, 1.0f, 1.0f));
    
    	// [UE4]Global Shader 파트에서 만든 함수. Texture의 내용을 OutputRenderTargetResource에 그려줌
    	DrawTestShaderRenderTarget_RenderThread(RHICmdList, OutputRenderTargetResource, FeatureLevel, TextureRenderTargetName
    		, FLinearColor(), Texture, ShaderStructData);
    }

     

    Compute Shader를 블루프린트로 호출할 수 있도록 함수 제공

    // GlobalShaderTestCode.h
    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;
    };
    
    UCLASS(MinimalAPI, meta = (ScriptName = "TestShaderLibrary"))
    class UGlobalTestShaderBlueprintLibrary : public UBlueprintFunctionLibrary
    {
    	GENERATED_UCLASS_BODY()
    
    	UFUNCTION(BlueprintCallable, Category = "GlobalShaderTestPlugin", meta = (WorldContext = "WorldContextObject"))
    	static void UseComputeShader(class UTextureRenderTarget2D* OutputRenderTarget, AActor* Actor, FGlobalShaderStructData ShaderStructData);
    };
    
    // GlobalShaderTestCode.cpp
    void UGlobalTestShaderBlueprintLibrary::UseComputeShader(class UTextureRenderTarget2D* OutputRenderTarget, AActor* Actor, FGlobalShaderStructData ShaderStructData)
    {
    	check(IsInGameThread());
    
    	if (!OutputRenderTarget)
    		return;
    
    	if (!Actor)
    		return;
    
    	FTextureRenderTargetResource* TextureRenderTargetResource = OutputRenderTarget->GameThread_GetRenderTargetResource();
    	UWorld* World = Actor->GetWorld();
    	ERHIFeatureLevel::Type FeatureLevel = World->Scene->GetFeatureLevel();
    	FName TextureRenderTargetName = OutputRenderTarget->GetFName();
    	ENQUEUE_RENDER_COMMAND(CaptureCommand)(
    		[TextureRenderTargetResource, FeatureLevel, ShaderStructData, TextureRenderTargetName](FRHICommandListImmediate& RHICmdList)
    		{
    			UseComputeShader_RenderThread(RHICmdList, TextureRenderTargetResource, ShaderStructData, FeatureLevel, TextureRenderTargetName);
    		}
    	);
    }

     

    RenderTarget의 RenderTargetFormat을 설정해준다. Compute Shader를 통해 만든 Texture와 포맷을 맞춤

     

    블루프린트 함수 UseComputeShader 호출

     

    Compute Shader에서 그린 Texture를 렌더타겟에 그린 후, Cube Actor의 Material로 사용

     

    StructuredBuffer 테스트로 입력값과 변경된 값 결과

    '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]Global Shader  (0) 2020.03.17

    댓글

Designed by Tistory & scahp.