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);
}

Comopute 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);
		}
	);
}