这一章中,我希望通过添加一个MeshPass的形式把标记了特定Tag的物体渲染成一张特定的RT作为蒙版。虽然CustomDepth之类的功能也能方便地做到这一点,但这里主要是想通过这个来学习如何添加一个MeshPass 这里参考了EMeshPass::EditorLevelInstance
的实现。
首先,我们先注册我们的MeshPass以及MeshProcessor 在MeshPass的相关的内容里把我们的MeshPass加上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 namespace EMeshPass { enum Type : uint8 { ... #if WITH_EDITOR HitProxy, HitProxyOpaqueOnly, EditorLevelInstance, EditorSelection, SpecialTagElement, #endif Num, NumBits = 6 , };} ... inline const TCHAR* GetMeshPassName (EMeshPass::Type MeshPass) { switch (MeshPass) { ... #if WITH_EDITOR case EMeshPass::HitProxy: return TEXT ("HitProxy" ); case EMeshPass::HitProxyOpaqueOnly: return TEXT ("HitProxyOpaqueOnly" ); case EMeshPass::EditorLevelInstance: return TEXT ("EditorLevelInstance" ); case EMeshPass::EditorSelection: return TEXT ("EditorSelection" ); case EMeshPass::SpecialTagElement: return TEXT ("SpecialTagElement" ); #endif } #if WITH_EDITOR static_assert (EMeshPass::Num == 30 + 4 , "Need to update switch(MeshPass) after changing EMeshPass" ); #else static_assert (EMeshPass::Num == 29 , "Need to update switch(MeshPass) after changing EMeshPass" ); #endif checkf (0 , TEXT ("Missing case for EMeshPass %u" ), (uint32)MeshPass); return nullptr ; } class ENGINE_API FPSOCollectorCreateManager { public : constexpr static uint32 MaxPSOCollectorCount = 34 ; ... };
然后仿照FEditorLevelInstanceMeshProcessor
写一个MeshProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class FSpecialTagElementMeshProcessor : public FSceneRenderingAllocatorObject<FSpecialTagElementMeshProcessor>, public FMeshPassProcessor { public : FSpecialTagElementMeshProcessor (const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext); virtual void AddMeshBatch (const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId = -1 ) override final ; FMeshPassProcessorRenderState PassDrawRenderState; private : bool TryAddMeshBatch (const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId, const FMaterialRenderProxy* MaterialRenderProxy, const FMaterial* Material) ; bool Process ( const FMeshBatch& MeshBatch, uint64 BatchElementMask, int32 StaticMeshId, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, const FMaterialRenderProxy& RESTRICT MaterialRenderProxy, const FMaterial& RESTRICT MaterialResource, ERasterizerFillMode MeshFillMode, ERasterizerCullMode MeshCullMode) ; int32 GetStencilValue (const FSceneView* View, const FPrimitiveSceneProxy* PrimitiveSceneProxy) ; };
大部分内容直接copyFEditorLevelInstanceMeshProcessor
的实现就行了,注意我们不需要判断物体是否是LevelInstance以及我们的stencil值不同(这里我们先暂时都返回1 )。 流程重点就是我们通过MeshPassProcessor的AddMeshBatch接口传来的MeshBatch以及其他信息,构建一个我们想要的MeshDrawCommands。 (直接用FEditorLevelInstanceMeshProcessor
实现的好处就是shader啥的都不用我们自己写了,简而言之就是偷个懒×) 参考FEditorLevelInstanceMeshProcessor
的实现时,我们还会发现一行很重要的代码:
1 FRegisterPassProcessorCreateFunction RegisterEditorLevelInstancePass (&CreateEditorLevelInstancePassProcessor, EShadingPath::Deferred, EMeshPass::EditorLevelInstance, EMeshPassFlags::MainView) ;
这个代码声明了一个全局变量,这个全局变量被声明的时候,通过构造函数把MeshPass和相应的MeshPassProcessor绑定到一起(一点吐槽:看UE代码的时候发现,UE还挺喜欢像这样在构造函数里面搞点东西)
然后,让标记了特定Tag的物体渲染成一张特定的蒙版 在不修改任何东西的前提下,到我们的MeshPassProcessor的AddMeshBatch是无法知道对应物体的Tag的,因此我们首先把Tag的值传进来,我们可以通过PrimitiveSceneProxy来完成这个目的。 在PrimitiveSceneProxy增加一个变量,并且在构造PrimitiveSceneProxy的时候把Component的Tags赋值给那个变量。 然后在FSpecialTagElementMeshProcessor::GetStencilValue
中把有特殊Tag的物体的stencil值设置为1。
做完上述工作之后,让标记了特定Tag的物体渲染成一张特定的蒙版的任务还差一步:把Pass加到我们的渲染流程里。 我们把这个Pass加到后处理Pass里。同样仿照添加EMeshPass::EditorLevelInstance
的AddVisualizeLevelInstancePass
。 在两者流程基本相同,这里暂时不考虑nanite物体(nanite物体因为和普通物体的渲染流程不同,要做额外处理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 BEGIN_SHADER_PARAMETER_STRUCT (FVisualizeSpecialTagElementPassParameters, ) SHADER_PARAMETER_STRUCT_INCLUDE (FSceneTextureShaderParameters, SceneTextures) SHADER_PARAMETER_STRUCT_INCLUDE (FInstanceCullingDrawParams, InstanceCullingDrawParams) RENDER_TARGET_BINDING_SLOTS () END_SHADER_PARAMETER_STRUCT () void AddVisualizeSpecialTagElementPass (FRDGBuilder& GraphBuilder, const FViewInfo& View, const FVisualizeLevelInstanceInputs& Inputs) { check (Inputs.SceneColor.IsValid ()); check (Inputs.SceneDepth.IsValid ()); RDG_EVENT_SCOPE (GraphBuilder, "Editor Visualize Special Tag Element" ); const uint32 NumSamples = View.GetSceneTexturesConfig ().NumSamples; const FViewInfo* EditorView = CreateEditorPrimitiveView (View, Inputs.SceneColor.ViewRect, NumSamples); FRDGTextureRef DepthStencilTexture = nullptr ; { { FRDGTextureDesc DepthStencilDesc = Inputs.SceneColor.Texture->Desc; DepthStencilDesc.Reset (); DepthStencilDesc.Format = PF_DepthStencil; DepthStencilDesc.ClearValue = FClearValueBinding ((float )ERHIZBuffer::FarPlane, 0 ); DepthStencilDesc.Flags = TexCreate_DepthStencilTargetable | TexCreate_ShaderResource; DepthStencilDesc.NumSamples = NumSamples; DepthStencilTexture = GraphBuilder.CreateTexture (DepthStencilDesc, TEXT ("LevelInstanceDepth" )); } FScene* Scene = View.Family->Scene->GetRenderScene (); const FScreenPassTextureViewport SceneColorViewport (Inputs.SceneColor) ; auto * PassParameters = GraphBuilder.AllocParameters <FVisualizeSpecialTagElementPassParameters>(); const_cast <FViewInfo&>(View).ParallelMeshDrawCommandPasses[EMeshPass::SpecialTagElement].BuildRenderingCommands (GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams); PassParameters->SceneTextures = Inputs.SceneTextures; PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding ( DepthStencilTexture, ERenderTargetLoadAction::EClear, ERenderTargetLoadAction::EClear, FExclusiveDepthStencil::DepthWrite_StencilWrite); GraphBuilder.AddPass ( RDG_EVENT_NAME ("LevelInstanceDepth %dx%d" , SceneColorViewport.Rect.Width (), SceneColorViewport.Rect.Height ()), PassParameters, ERDGPassFlags::Raster, [&View, SceneColorViewport, DepthStencilTexture, PassParameters](FRHICommandListImmediate& RHICmdList) { RHICmdList.SetViewport (SceneColorViewport.Rect.Min.X, SceneColorViewport.Rect.Min.Y, 0.0f , SceneColorViewport.Rect.Max.X, SceneColorViewport.Rect.Max.Y, 1.0f ); { SCOPED_DRAW_EVENT (RHICmdList, EditorLevelInstance); View.ParallelMeshDrawCommandPasses[EMeshPass::SpecialTagElement].DispatchDraw (nullptr , RHICmdList, &PassParameters->InstanceCullingDrawParams); } }); }}
然后在后处理流程里调用我们的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void AddPostProcessingPasses ( FRDGBuilder& GraphBuilder, const FViewInfo& View, int32 ViewIndex, bool bAnyLumenActive, EReflectionsMethod ReflectionsMethod, const FPostProcessingInputs& Inputs, const Nanite::FRasterResults* NaniteRasterResults, FInstanceCullingManager& InstanceCullingManager, FVirtualShadowMapArray* VirtualShadowMapArray, FLumenSceneFrameTemporaries& LumenFrameTemporaries, const FSceneWithoutWaterTextures& SceneWithoutWaterTextures, FScreenPassTexture TSRMoireInput) {... if (PassSequence.IsEnabled (EPass::VisualizeSpecialTagElement)) { FVisualizeSpecialTagElement PassInputs; PassSequence.AcceptOverrideIfLastPass (EPass::VisualizeSpecialTagElement, PassInputs.OverrideOutput); PassInputs.SceneColor = SceneColor; PassInputs.SceneDepth = SceneDepth; PassInputs.SceneTextures.SceneTextures = Inputs.SceneTextures; SceneColor = AddVisualizeSpecialTagElementPass (GraphBuilder, View, PassInputs); } ... }
同时别忘了在计算可见性的地方让Mesh能够加入我们的MeshPass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void MarkRelevant () {... { DrawCommandPacket.AddCommandsForMesh (PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::SpecialTagElement); } ... } void ComputeDynamicMeshRelevance (EShadingPath ShadingPath, bool bAddLightmapDensityCommands, const FPrimitiveViewRelevance& ViewRelevance, const FMeshBatchAndRelevance& MeshBatch, FViewInfo& View, FMeshPassMask& PassMask, FPrimitiveSceneInfo* PrimitiveSceneInfo, const FPrimitiveBounds& Bounds) {if (ViewRelevance.bDrawRelevance) { ... PassMask.Set (EMeshPass::SpecialTagElement); View.NumVisibleDynamicMeshElements[EMeshPass::SpecialTagElement] += NumElements; } }
编译运行,看看我们的结果
备注:调试的时候遇到一个很麻烦的bug,VS输出的SV_Position值有问题,是NAN导致结果出错。目前排查到的原因是LocalToWorld矩阵的值有问题,具体是哪步的值开始传出问题的还没继续排查。因此把Pass改成在BasePass的时候调用了(这个时候的值没问题)。找到原因了,和后处理的顺序有关,把相应的Pass的顺序提前就好了
最后,代码添加一个MeshPass学习笔记代码记录