ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [UE4]Geometry Shader
    UE4 & 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

    댓글

Designed by Tistory & scahp.