-
[UE4]Geometry ShaderUE4 & UE5/Rendering 2020. 4. 5. 02:46
아래 사이트의 Shader 파트를 참고하여 작성하였습니다.
출처 : https://zhuanlan.zhihu.com/p/100834351
환경
Unreal Engine 4.24.3
목표
1. Geometry Shader를 사용합니다.
2. 별도의 렌더 패스를 만들어서 예제를 출력해봅니다.
C++에서 Geometry Shader 생성
// C:\Github\UE4\Engine\Source\Runtime\Renderer\Private\MyGS\MyGS.cpp #include "CoreMinimal.h" #include "SceneRendering.h" #include "RHICommandList.h" #include "Shader.h" #include "RHIStaticStates.h" #include "ScenePrivate.h" #include "DeferredShadingRenderer.h" // Vertex Shader class FMyGS_VS : public FGlobalShader { DECLARE_SHADER_TYPE(FMyGS_VS, Global); public: FMyGS_VS(){} FMyGS_VS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) { } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } static bool ShouldCache(EShaderPlatform Platform) { return true; } virtual bool Serialize(FArchive& Ar) override { bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar); return bShaderHasOutdatedParameters; } void SetParameters(FRHICommandList& RHICmdList, const FViewInfo& View) { FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, GetVertexShader(), View.ViewUniformBuffer); } }; IMPLEMENT_SHADER_TYPE(, FMyGS_VS, TEXT("/Engine/Private/MyGS/MyGS.usf"), TEXT("MainVS"), SF_Vertex); // Pixel Shader class FMyGS_PS : public FGlobalShader { DECLARE_SHADER_TYPE(FMyGS_PS, Global); public: FMyGS_PS() {} FMyGS_PS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) { } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } static bool ShouldCache(EShaderPlatform Platform) { return true; } virtual bool Serialize(FArchive& Ar) override { bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar); return bShaderHasOutdatedParameters; } void SetParameters(FRHICommandList& RHICmdList, const FViewInfo& View) { FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, GetPixelShader(), View.ViewUniformBuffer); } }; IMPLEMENT_SHADER_TYPE(, FMyGS_PS, TEXT("/Engine/Private/MyGS/MyGS.usf"), TEXT("MainPS"), SF_Pixel); // Geometry Shader class FMyGS_GS : public FGlobalShader { DECLARE_SHADER_TYPE(FMyGS_GS, Global); public: FMyGS_GS() {} FMyGS_GS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FGlobalShader(Initializer) { } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5); } static bool ShouldCache(EShaderPlatform Platform) { return true; } virtual bool Serialize(FArchive& Ar) override { bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar); return bShaderHasOutdatedParameters; } void SetParameters(FRHICommandList& RHICmdList, const FViewInfo& View) { FGlobalShader::SetParameters<FViewUniformShaderParameters>(RHICmdList, GetGeometryShader(), View.ViewUniformBuffer); } }; IMPLEMENT_SHADER_TYPE(, FMyGS_GS, TEXT("/Engine/Private/MyGS/MyGS.usf"), TEXT("MainGS"), SF_Geometry); // 삼각형 2개로 Quad를 이루는 Geometry 정보를 가진 클래스 class FDebugPane { public: FDebugPane() { Initialized = false; } ~FDebugPane() { VertexBufferRHI.SafeRelease(); IndexBufferRHI.SafeRelease(); } void FillRawData() { VertexBuffer = { FVector(0.0f, 0.0f, 0.0f), FVector(100.0f, 0.0f, 0.0f), FVector(100.0f, 100.0f, 0.0f), FVector(0.0f, 100.0f, 0.0f) }; IndexBuffer = { 0, 1, 2, 0, 2, 3 }; } void EmptyRawData() { VertexBuffer.Empty(); IndexBuffer.Empty(); } void Init() { FillRawData(); VertexCount = static_cast<uint32>(VertexBuffer.Num()); PrimitiveCount = static_cast<uint32>(IndexBuffer.Num() / 3); // GPU Vertex Buffer { TStaticMeshVertexData<FVector> VertexData(false); Stride = VertexData.GetStride(); VertexData.ResizeBuffer(VertexBuffer.Num()); uint8* Data = VertexData.GetDataPointer(); const uint8* InData = (const uint8*)&(VertexBuffer[0]); FMemory::Memcpy(Data, InData, Stride * VertexBuffer.Num()); FResourceArrayInterface* ResourceArray = VertexData.GetResourceArray(); FRHIResourceCreateInfo CreateInfo(ResourceArray); VertexBufferRHI = RHICreateVertexBuffer(ResourceArray->GetResourceDataSize(), BUF_Static, CreateInfo); } { TResourceArray<uint16, INDEXBUFFER_ALIGNMENT> IndexArray; IndexArray.AddUninitialized(IndexBuffer.Num()); FMemory::Memcpy(IndexArray.GetData(), (void*)(&(IndexBuffer[0])), IndexBuffer.Num() * sizeof(uint16)); // Create index buffer, Fill buffer with initial data upon creation. FRHIResourceCreateInfo CreateInfo(&IndexArray); IndexBufferRHI = RHICreateIndexBuffer(sizeof(uint16), IndexArray.GetResourceDataSize(), BUF_Static, CreateInfo); } EmptyRawData(); Initialized = true; } TArray<FVector> VertexBuffer; TArray<uint16> IndexBuffer; uint32 Stride; bool Initialized; uint32 VertexCount; uint32 PrimitiveCount; FVertexBufferRHIRef VertexBufferRHI; FIndexBufferRHIRef IndexBufferRHI; }; FDebugPane DebugMesh; // FDeferredShadingSceneRenderer::Render 에서 사용할 렌더패스 함수 void FDeferredShadingSceneRenderer::RenderMyMeshPass(FRHICommandListImmediate& RHICmdList, const TArrayView<const FViewInfo*> PassViews) { check(RHICmdList.IsOutsideRenderPass()); TShaderMap<FGlobalShaderType>* ShaderMap = GetGlobalShaderMap(FeatureLevel); FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList); SceneContext.BeginRenderingSceneColor(RHICmdList, ESimpleRenderTargetMode::EExistingColorAndDepth, FExclusiveDepthStencil::DepthRead_StencilWrite, true); FGraphicsPipelineStateInitializer PSOInit; RHICmdList.ApplyCachedRenderTargets(PSOInit); PSOInit.RasterizerState = TStaticRasterizerState<FM_Wireframe, CM_None, false, false>::GetRHI(); PSOInit.BlendState = TStaticBlendState<>::GetRHI(); PSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_GreaterEqual>::GetRHI(); PSOInit.PrimitiveType = EPrimitiveType::PT_TriangleList; PSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector3(); TShaderMapRef<FMyGS_VS> VS(ShaderMap); TShaderMapRef<FMyGS_PS> PS(ShaderMap); TShaderMapRef<FMyGS_GS> GS(ShaderMap); PSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VS); PSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PS); PSOInit.BoundShaderState.GeometryShaderRHI = GETSAFERHISHADER_GEOMETRY(*GS); SetGraphicsPipelineState(RHICmdList, PSOInit); for (int32 i = 0; i < PassViews.Num(); ++i) { const FViewInfo* View = PassViews[i]; if (!DebugMesh.Initialized) DebugMesh.Init(); RHICmdList.SetViewport(View->ViewRect.Min.X, View->ViewRect.Min.Y, 0.0f, View->ViewRect.Max.X, View->ViewRect.Max.Y, 1.0f); GS->SetParameters(RHICmdList, *View); RHICmdList.SetStreamSource(0, DebugMesh.VertexBufferRHI, 0); RHICmdList.DrawIndexedPrimitive(DebugMesh.IndexBufferRHI, 0, 0, DebugMesh.VertexCount, 0, DebugMesh.PrimitiveCount, 1); } SceneContext.FinishRenderingSceneColor(RHICmdList); }
커스텀 렌더 패스 추가
// Engine\Source\Runtime\Renderer\Private\DeferredShadingRenderer.h ... class FDeferredShadingSceneRenderer : public FSceneRenderer { public: // 함수 선언 추가 void RenderMyMeshPass(FRHICommandListImmediate& RHICmdList, const TArrayView<const FViewInfo*> PassViews); ... // Engine\Source\Runtime\Renderer\Private\DeferredShadingRenderer.cpp // 적절한 위치에 아래 코드 추가(내경우엔 Fog Pass 뒤에 추가함) ... checkSlow(RHICmdList.IsOutsideRenderPass()); // Begin render my pass TArray<const FViewInfo*> ViewList; for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex) ViewList.Add(&Views[ViewIndex]); RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_MyMeshPass)); RenderMyMeshPass(RHICmdList, ViewList); RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_AfterMyMeshPass)); ServiceLocalQueue(); // RHI Thread가 별도로 있다면, 렌더링 도중 주기적으로 렌더커맨드를 RHI로 Dispatch 함. // End render my pass checkSlow(RHICmdList.IsOutsideRenderPass()); ...
Geometry Shader .usf 작성
// Engine\Shaders\Private\MyGS\MyGS.usf #include "../Common.ush" struct VSToGS { float4 WPosition : SV_POSITION; }; struct GSToPS { float4 HPosition : SV_POSITION; }; // VertexShader void MainVS( in float3 Position : ATTRIBUTE0, out VSToGS Output ) { Output.WPosition = Output.WPosition = float4(Position + float3(0, 0, 10), 1); } // Geometry Shader // 이 Geometry Shader는 3개의 Triangle Vertex Input을 받아서 9개의 Vertex Output(총 3개의 Triangle)을 출력하는 Geometry Shader. [maxvertexcount(9)] // 총 9개의 Vertex를 만들 수 있음. void MainGS(triangle VSToGS input[3], inout TriangleStream<GSToPS> OutPos) // Triangle 형태로 Output을 만듬 { VSToGS vertexes[6]; GSToPS verts[6]; int i = 0; // 필요한 추가 Vetex를 생성함 [unroll] // for loop을 풀어줌. 아래의 for(0~3까지)는 for가 아니라 이 반복문의 내용을 3번 사용. for (i = 0; i < 3; ++i) { vertexes[i] = input[i]; vertexes[i + 3].WPosition = (input[i].WPosition + input[(i + 1) % 3].WPosition) / 2.0f; } // 모든 Vetex를 World Space -> Clip Space로 이동 [unroll] for (i = 0; i < 6; ++i) { verts[i].HPosition = mul(float4(vertexes[i].WPosition.xyz, 1), View.WorldToClip); } // Primitive 생성 [unroll] for (i = 0; i < 3; ++i) { OutPos.Append(verts[i]); // Vertex 1 추가 OutPos.Append(verts[3 + i]); // Vertex 2 추가 OutPos.Append(verts[(i + 2) % 3 + 3]); // Vertex 3 추가 OutPos.RestartStrip(); // 프리미티브 출력 (여기서 삼각형 하나가 만들어 짐) } } // PixelShader void MainPS(out float4 OutColor : SV_Target0) { OutColor = float4(1, 0, 0, 1); }
결과
실제 입력은 삼각형 2개로 구성된 QUAD 입니다. Geometry Shader에서 삼각형 당 3개의 Triangle을 생성해내는 방식으로 아래 결과를 출력합니다. 아래 이미지의 파란색 마크의 삼각형들이 첫번째 삼각형을 통해 만들어진 삼각형, 그리고 초록색 마크의 삼각형들이 두번째 삼각형을 사용해 만들어진 삼각형입니다.
'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]Compute Shader (2) 2020.03.20 [UE4]Global Shader (0) 2020.03.17