# MeshDraw学习笔记 ## 前言 源码版4.27.0 参考文章Yivanlee的MeshDraw系列文章。 ## 图元渲染数据收集 - FDeferredShadingSceneRenderer::Render() - InitViews() - ComputeViewVisibility() - GatherDynamicMeshElements() - GetDynamicMeshElements() InitViews():计算可见性以及初始化胶囊阴影、天空环境图、大气雾、体积雾。 GatherDynamicMeshElements():遍历场景中的所有图元类,调用`GetDynamicMeshElements()`接口函数获取渲染数据,之后调用`FMeshElementCollector`的`AllocateMesh()`创建一块FMeshBatch类型的内存并且使用渲染数据进行填充。 `FMeshBatch`承载`MaterialRenderProxy`以及其他渲染数据,比如: - FVertexFactory - FMaterialRenderProxy - FLightCacheInterface - uint32 CastShadow : 1; // Whether it can be used in shadow renderpasses. - uint32 bUseForMaterial : 1; // Whether it can be used in renderpasses requiring material outputs. - uint32 bUseForDepthPass : 1; // Whether it can be used in depth pass. - uint32 bUseAsOccluder : 1; // Hint whether this mesh is a good occluder. - uint32 bWireframe `FPrimitiveSceneProxy::GetDynamicMeshElements()` FPrimitiveSceneProxy为了解决游戏线程与渲染线程之间数据传递造成的锁死问题而诞生的方案,可以理解为在渲染线程中的Scene镜像。 无论是StaticMesh还是SkeletonMesh都重写了基类的UPrimitiveComponent::CreateSceneProxy()创建对应的SceneProxy类,之后再用此提交渲染数据的,提交渲染信息与请求的逻辑位于GetDynamicMeshElements()。 该函数函数在渲染线程中运行,根据情况使用不通的FMaterialRenderProxy子类传递给FMeshBatch与FMeshElementCollector(FMaterialRenderProxy及其子类可以看做Material信息镜像)。这些情况大致包含: - DebugView - 渲染网格模式 - Lod对应的Material - 顶点色可视 这一步可以理解为传递Material给MeshDraw框架。 ## MeshDraw渲染 在`ComputeViewVisibility()`执行完`GatherDynamicMeshElements()`收集完图元渲染数据后会对每个需要渲染的View调用`SetupMeshPass()`。`SetupMeshPass()`会遍历`EMeshPass`中所定义的枚举,再使用对应创建函数来构建FMeshPassProcessor,最后执行`DispatchPassSetup()`填充所需的渲染相关信息后渲染线程中创建当前Pass的绘制任务。 EMeshPass定义了Pass: ```js DepthPass, BasePass, AnisotropyPass, SkyPass, SingleLayerWaterPass, CSMShadowDepth, Distortion, Velocity, TranslucentVelocity, TranslucencyStandard, TranslucencyAfterDOF, TranslucencyAfterDOFModulate, TranslucencyAll, /** Drawing all translucency, regardless of separate or standard. Used when drawing translucency outside of the main renderer, eg FRendererModule::DrawTile. */ LightmapDensity, DebugViewMode, /** Any of EDebugViewShaderMode */ CustomDepth, MobileBasePassCSM, /** Mobile base pass with CSM shading enabled */ MobileInverseOpacity, /** Mobile specific scene capture, Non-cached */ VirtualTexture, DitheredLODFadingOutMaskPass ``` ### FMeshPassProcessor `FMeshPassProcessor`是Mesh处理器的基类,主要作用是设置渲染状态、绑定Shader与UniformStructBuffer,最后生成MeshDrawCommands并且加入绘制队列。只要与模型相关的Pass都会继承该类,在派生类中都会重写`AddMeshBatch()`,一般会在对应的生成MeshDrawCommands函数或是`DrawDynamicMeshPass()`中的回调函数中调用。以及实现具体的处理函数`Process()`。 #### DrawDynamicMeshPass 该函数中有一个回调函数, 回调函数的逻辑顺序为: 1. 使用FScene、FSceneView、FMeshPassProcessorRenderState、EDepthDrawingMode、FMeshPassDrawListContext等变量创建一个FMeshPassProcessor。 2. 之后按照有效的View,使用AddMeshBatch()往FMeshPassProcessor中添加MeshBatch。 #### AddMeshBatch 其作用为往一个Pass中增加FMeshBatch。 主要逻辑: 1. 判断是否需要绘制后,进行寻找FMaterial递归。 2. 从FMaterialRenderProxy中寻找FMaterial,如FMaterial无效则从父类寻找,直至找到为止。(底层为各个材质模型的默认材质) 3. 找到有效FMaterial后调用TryAddMeshBatch()。 #### TryAddMeshBatch 收集BlendMode、MeshDrawingPolic、RasterizerFillMode、RasterizerCullMode等所需变量后传递给处理函数`Process()`。在`Process()`中取得所需Shader与渲染数据后(FMeshPassProcessorRenderState、FMeshDrawCommandSortKey、FMeshMaterialShaderElementData等)调用`BuildMeshDrawCommands()`创建MeshDrawCommands。 以FDepthPassMeshProcessor为例:`Process()`的主要逻辑顺序为取得所需的Shader,包括顶点、Hull、Domain、Vertex、Pixel。初始化MeshMaterial数据,BuildMeshDrawCommands,构建MeshDraw命令。`Process()`同时也是个模板函数(根据不同渲染需求构建对应的MeshDrawCommand),用来切换构建的MeshDrawCommand中的EMeshPassFeatures形参,用于设置顶点输入流类型(Default、PositionOnly、PositionAndNormalOnly)。 `FMeshPassProcessorRenderState`是MeshPassProcessor的渲染状态集,。存储信息如下: - FRHIBlendState* BlendState; - FRHIDepthStencilState* DepthStencilState; - FExclusiveDepthStencil::Type DepthStencilAccess; - FRHIUniformBuffer* ViewUniformBuffer; - FRHIUniformBuffer* InstancedViewUniformBuffer; - FRHIUniformBuffer* ReflectionCaptureUniformBuffer; - FRHIUniformBuffer* PassUniformBuffer; - uint32 StencilRef; ### BuildMeshDrawCommands BuildMeshDrawCommands()大致逻辑为: 1. 创建`FMeshDrawCommand`对象。以下简称为MDC。 2. 根据`FMeshPassProcessorRenderState`中的`StencilRef`来设置MDC的模板index。 3. 创建`FGraphicsMinimalPipelineStateInitializer`对象,设置PrimitiveType、ImmutableSamplerState;根据PassShadersType(模板参数)给`FGraphicsMinimalPipelineStateInitializer`引用设置对应Shader的ShaderResource与ShaderIndex;设置MDC的RasterizerState、BlendState、DepthStencilState、DrawShadingRate;通过VertexFactory来设置MDC的PrimitiveIdStreamIndex。 4. 判断PassShadersType(模板参数)是那种类型的Shader之后,取得对应`FMeshDrawSingleShaderBindings`,最后将`FShaderUniformBufferParameter`、`FViewUniformShaderParameters`、`FDistanceCullFadeUniformShaderParameters`、`FDitherUniformShaderParameters`、`FInstancedViewUniformShaderParameters`加入`FMeshDrawSingleShaderBindings`中。(`FShaderUniformBufferParameter`会在对应Shader类中绑定实际的UniformBuffer) 5. 遍历`FMeshBatch`中存储的所有FMeshBatchElement,将之前的MDC对象加入`DrawListStorage`中并取得其引用;根据PassShadersType(模板参数)从MDC引用取得对应`FMeshDrawSingleShaderBindings`,最后将`FPrimitiveUniformShaderParameters`加入`FMeshDrawSingleShaderBindings`中。 6. 结束当前MDC构建并且将其加入`DrawListContext`的绘制列表中。 ## 场景与FMeshPassProcessor的关系 在FScene::AddPrimitive(UPrimitiveComponent* Primitive)创建图元类对应的场景代理,计算矩阵、边界盒来构建`FCreateRenderThreadParameters`对象,最后向渲染线程加入图元场景信息。 在ActorComponents的UpdateAllPrimitiveSceneInfosForScenes()会在渲染线程执行UpdateAllPrimitiveSceneInfos()。UpdateAllPrimitiveSceneInfos()=》AddToScene()=>AddStaticMeshes()=>CacheMeshDrawCommands()中遍历所有类型的Pass,并且创建对应的`FMeshPassProcessor`然后调用AddMeshBatch()。 ## MeshDraw与RGD MeshDraw部分(不考虑Shader)以SingleLayerWater为例子: - 构建FSingleLayerWaterPassMeshProcessor类 - 在构造函数中设置PassDrawRenderState。(CW_RGBA, BO_Add, BF_One, BF_InverseSourceAlpha) - 重写AddMeshBatch(),收集OverrideSettings、MeshFillMode、MeshCullMode、MaterialRenderProxy后传入Process(). - 实现Process(),取得Shader、初始化ShaderElementData、计算SortKey之后,调用BuildMeshDrawCommands()构建MeshDrawCommands。 - 实现CreateSingleLayerWaterPassProcessor()与对应的FRegisterPassProcessorCreateFunction,以用来创建FSingleLayerWaterPassMeshProcessor。 调用的逻辑位于FDeferredShadingSceneRenderer::RenderSingleLayerWaterInner: - 取得GBuffer并绑定深度模板;从RTPool中取得一个纯白贴图(WhiteDummy) - 遍历有效View开始渲染 - 使用上述2个RT与FOpaqueBasePassUniformParameters填充FSingleLayerWaterPassParameters - 使用RDG创建一个Pass里面执行:更新ViewUniformBuffer后,用上述两个UniformBuffer构建FRDGParallelCommandListSet,最后使用View.ParallelMeshDrawCommandPasses[EMassPass::SingleLayerWaterPass].DispatchDraw()进行绘制。 ## 调整渲染方式以实现背面剔除 目标是设置为非DoubleSide以及ReverseCullMode模式,一些给顶点工厂添加数据 Mesh.bDisableBackfaceCulling Mesh.ReverseCulling 重写FSkeletalMeshSceneProxy::GetDynamicElementsSection() ```c# ERasterizerCullMode FMeshPassProcessor::ComputeMeshCullMode(const FMeshBatch& Mesh, const FMaterial& InMaterialResource, const FMeshDrawingPolicyOverrideSettings& InOverrideSettings) { const bool bMaterialResourceIsTwoSided = InMaterialResource.IsTwoSided(); const bool bInTwoSidedOverride = !!(InOverrideSettings.MeshOverrideFlags & EDrawingPolicyOverrideFlags::TwoSided); const bool bInReverseCullModeOverride = !!(InOverrideSettings.MeshOverrideFlags & EDrawingPolicyOverrideFlags::ReverseCullMode); const bool bIsTwoSided = (bMaterialResourceIsTwoSided || bInTwoSidedOverride); const bool bMeshRenderTwoSided = bIsTwoSided || bInTwoSidedOverride; return bMeshRenderTwoSided ? CM_None : (bInReverseCullModeOverride ? CM_CCW : CM_CW); } ``` ## 修改ShaderModel 修改Pin 添加ShaderModel枚举 BasePassPixelShader.usf中的FPixelShaderInOut_MainPS()