2023-06-29 11:55:02 +08:00

9.6 KiB
Raw Blame History

MeshDraw学习笔记

前言

源码版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与FMeshElementCollectorFMaterialRenderProxy及其子类可以看做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()进行绘制。

调整渲染方式以实现背面剔除

目标是设置为非DoubleSide以及ReverseCullMode模式一些给顶点工厂添加数据 Mesh.bDisableBackfaceCulling Mesh.ReverseCulling

重写FSkeletalMeshSceneProxy::GetDynamicElementsSection()

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()