-
[UE4]Compute ShaderUE4 & 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