BlueRose
文章97
标签28
分类7
MeshDraw渲染流程学习笔记

MeshDraw渲染流程学习笔记

前言

MeshDraw是UE4用于渲染Mesh相关Pass的框架,他主要用于将Mesh渲染到RT上,之后在RDG中进行进一步的渲染。

使用引擎版本:源码版4.27.0

参考Yivanlee的MeshDraw系列文章。

图元渲染数据收集

  • FDeferredShadingSceneRenderer::Render()
  • InitViews()
  • ComputeViewVisibility()
  • GatherDynamicMeshElements()
  • GetDynamicMeshElements()

InitViews():计算可见性以及初始化胶囊阴影、天空环境图、大气雾、体积雾。
GatherDynamicMeshElements():遍历场景中的所有图元类,调用GetDynamicMeshElements()接口函数获取渲染数据,之后调用FMeshElementCollectorAllocateMesh()创建一块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:

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,最后将FShaderUniformBufferParameterFViewUniformShaderParametersFDistanceCullFadeUniformShaderParametersFDitherUniformShaderParametersFInstancedViewUniformShaderParameters加入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()进行绘制。