虚幻引擎之自定义MeshPass

虚幻引擎之自定义MeshPass

一、前言

虚幻引擎之SecondPass支持 中,实现了给一个Mesh多添加一个材质Slot,使Mesh可以再以额外的材质渲染一次。

同时,提到了 网格渲染管线(Mesh Drawing Pipeline),这是虚幻进行收集Mesh,生成DrawCommand的一套流程。

虚幻引擎之自定义着色模型(ShadingModel) 中,提到了当前虚幻引擎中的着色模型,并介绍了如何新增一个自定义的着色模型。

在本文中,笔者将介绍了一下虚幻中的渲染Pass。

一个渲染Pass,可以理解成为一次绘制。

虚幻中存在非常多的渲染Pass,包含了PreZPass,ShadowPass,BasePass,LightingPass等等。

这些Pass负责各自的功能,例如渲染深度图,渲染阴影图,渲染几何数据,渲染光照。

将这些Pass按照一定的顺序进行组织,从而实现了整个虚拟世界的渲染。

本文,首先会对渲染Pass进行简单的介绍,接着介绍如何自定义MeshPass,最后对BasePass、LightingPass的流程和着色器进行简要介绍。

本文基于的引擎代码为4.26版本,管线为延迟渲染管线。

二、虚幻MeshPass

虚幻中的Shader分为:GlobalShader(全局着色器)和 MaterialShader(材质着色器)

其中:

GlobalShader通常作用对象是屏幕空间,而非每个Mesh对象。

例如,延迟渲染管线中的LightingPass,其像素着色器为FDeferredLightPS。

/** A pixel shader for rendering the light in a deferred pass. */
class FDeferredLightPS : public FGlobalShader
{
    // ...省略部分源码
};

MaterialShader的作用对象为每个Mesh对象。

例如,延迟渲染管线中的BasePass,其顶点着色器TBasePassVS,可以看到其继承自FMeshMaterialShader。

/**
 * The base shader type for vertex shaders that render the emissive color, and light-mapped/ambient lighting of a mesh.
 * The base type is shared between the versions with and without atmospheric fog.
 */
template<typename LightMapPolicyType>
class TBasePassVertexShaderPolicyParamType : public FMeshMaterialShader, public LightMapPolicyType::VertexParametersType
{
    // ...省略部分源码 
};

/**
 * The base shader type for vertex shaders that render the emissive color, and light-mapped/ambient lighting of a mesh.
 * The base type is shared between the versions with and without atmospheric fog.
 */

template<typename LightMapPolicyType>
class TBasePassVertexShaderBaseType : public TBasePassVertexShaderPolicyParamType<LightMapPolicyType>
{
    // ...省略部分源码 
};

template<typename LightMapPolicyType, bool bEnableAtmosphericFog>
class TBasePassVS : public TBasePassVertexShaderBaseType<LightMapPolicyType>
{
    // ...省略部分源码 
};

本文介绍的自定义Pass是基于MeshMaterialShader,即作用的对象是场景中的每个Mesh对象。

这类直接作用于Mesh的Pass,称作Mesh Pass,在虚幻中有以下这些:

/** Mesh pass types supported. */
namespace EMeshPass
{
	enum Type
	{
		// 深度
		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, /** A mini depth pass used to mark pixels with dithered LOD fading out. Currently only used by ray tracing shadows. */

#if WITH_EDITOR
		// 编辑器模式的Pass
		HitProxy,
		HitProxyOpaqueOnly,
		EditorSelection,
#endif
        
		Num,
		NumBits = 5,
	};
}

其中,对于每个MeshPass都对应一个PassProcessor。

他们共同的基类为:FMeshPassProcessor。

如注释所言,FMeshPassProcessor是所有MessProcessors的基类,

它的工作就是将FMeshBatch绘制描述转换为FMeshDrawCommands。

/** 
 * Base class of mesh processors, whose job is to transform FMeshBatch draw descriptions received from scene proxy implementations into FMeshDrawCommands ready for the RHI command list
 */
class FMeshPassProcessor
{
public:
	// 场景、View、Context
	const FScene* RESTRICT Scene;
	ERHIFeatureLevel::Type FeatureLevel;
	const FSceneView* ViewIfDynamicMeshCommand;
	FMeshPassDrawListContext* DrawListContext;

	RENDERER_API FMeshPassProcessor(const FScene* InScene, ERHIFeatureLevel::Type InFeatureLevel, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext);

	virtual ~FMeshPassProcessor() {}

	void SetDrawListContext(FMeshPassDrawListContext* InDrawListContext)
	{
		DrawListContext = InDrawListContext;
	}

	// FMeshPassProcessor interface
	// Add a FMeshBatch to the pass
	// 添加一个meshBatch到pass
	virtual void AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId = -1) = 0;

	static FORCEINLINE_DEBUGGABLE ERasterizerCullMode InverseCullMode(ERasterizerCullMode CullMode)
	{
		return CullMode == CM_None ? CM_None : (CullMode == CM_CCW ? CM_CW : CM_CCW);
	}

	struct FMeshDrawingPolicyOverrideSettings
	{
		EDrawingPolicyOverrideFlags	MeshOverrideFlags = EDrawingPolicyOverrideFlags::None;
		EPrimitiveType				MeshPrimitiveType = PT_TriangleList;
	};

	RENDERER_API static FMeshDrawingPolicyOverrideSettings ComputeMeshOverrideSettings(const FMeshBatch& Mesh);
	RENDERER_API static ERasterizerFillMode ComputeMeshFillMode(const FMeshBatch& Mesh, const FMaterial& InMaterialResource, const FMeshDrawingPolicyOverrideSettings& InOverrideSettings);
	RENDERER_API static ERasterizerCullMode ComputeMeshCullMode(const FMeshBatch& Mesh, const FMaterial& InMaterialResource, const FMeshDrawingPolicyOverrideSettings& InOverrideSettings);

	//  将1个FMeshBatch转换成1或多个MeshDrawCommands
	template<typename PassShadersType, typename ShaderElementDataType>
	void BuildMeshDrawCommands(
		const FMeshBatch& RESTRICT MeshBatch,
		uint64 BatchElementMask,
		const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
		const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
		const FMaterial& RESTRICT MaterialResource,
		// 渲染状态
		const FMeshPassProcessorRenderState& RESTRICT DrawRenderState,
		PassShadersType PassShaders,
		ERasterizerFillMode MeshFillMode,
		ERasterizerCullMode MeshCullMode,
		FMeshDrawCommandSortKey SortKey,
		EMeshPassFeatures MeshPassFeatures,
		const ShaderElementDataType& ShaderElementData);

protected:
	RENDERER_API void GetDrawCommandPrimitiveId(
		const FPrimitiveSceneInfo* RESTRICT PrimitiveSceneInfo,
		const FMeshBatchElement& BatchElement,
		int32& DrawPrimitiveId,
		int32& ScenePrimitiveId) const;
};

例如,BasePass的Processor定义如下:

class FBasePassMeshProcessor : public FMeshPassProcessor
{
public:
	enum class EFlags
	{
		None = 0,

		// Informs the processor whether a depth-stencil target is bound when processed draw commands are issued.
		CanUseDepthStencil = (1 << 0),
		bRequires128bitRT = (1 << 1)
	};

	FBasePassMeshProcessor(
		const FScene* InScene,
		ERHIFeatureLevel::Type InFeatureLevel,
		const FSceneView* InViewIfDynamicMeshCommand,
		const FMeshPassProcessorRenderState& InDrawRenderState,
		FMeshPassDrawListContext* InDrawListContext,
		EFlags Flags,
		ETranslucencyPass::Type InTranslucencyPassType = ETranslucencyPass::TPT_MAX);

	virtual void AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId = -1) override final;

	FMeshPassProcessorRenderState PassDrawRenderState;

	FORCEINLINE_DEBUGGABLE void Set128BitRequirement(const bool Required)
	{
		bRequiresExplicit128bitRT = Required;
	}

	FORCEINLINE_DEBUGGABLE bool Get128BitRequirement() const
	{
		return bRequiresExplicit128bitRT;
	}

private:

	void AddMeshBatchForSimpleForwardShading(
		const FMeshBatch& RESTRICT MeshBatch,
		uint64 BatchElementMask,
		int32 StaticMeshId,
		const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
		const FMaterialRenderProxy& MaterialRenderProxy,
		const FMaterial& Material,
		const FLightMapInteraction& LightMapInteraction,
		bool bIsLitMaterial,
		bool bAllowStaticLighting,
		bool bUseVolumetricLightmap,
		bool bAllowIndirectLightingCache,
		ERasterizerFillMode MeshFillMode,
		ERasterizerCullMode MeshCullMode);

	template<typename LightMapPolicyType>
	void Process(
		const FMeshBatch& RESTRICT MeshBatch,
		uint64 BatchElementMask,
		int32 StaticMeshId,
		const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
		const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
		const FMaterial& RESTRICT MaterialResource,
		EBlendMode BlendMode,
		FMaterialShadingModelField ShadingModels,
		const LightMapPolicyType& RESTRICT LightMapPolicy,
		const typename LightMapPolicyType::ElementDataType& RESTRICT LightMapElementData,
		ERasterizerFillMode MeshFillMode,
		ERasterizerCullMode MeshCullMode);

	const ETranslucencyPass::Type TranslucencyPassType;
	const bool bTranslucentBasePass;
	const bool bEnableReceiveDecalOutput;
	EDepthDrawingMode EarlyZPassMode;
	bool bRequiresExplicit128bitRT;
};

并且,每个PassProcessor都会有一个FMeshPassProcessorRenderState类型的实例输入,用来设置该Pass相关的状态、UniformBuffer等。

FMeshPassProcessorRenderState可以将一系列渲染状态重写,传递给Mesh Pass Processor,可以用于在外部进行配置。

/**
 * A set of render state overrides passed into a Mesh Pass Processor, so it can be configured from the outside.
 */
struct FMeshPassProcessorRenderState
{
    // ...省略部分源码
private:
    // 混合状态
	FRHIBlendState*					BlendState;
	// 深度模板状态
	FRHIDepthStencilState*			DepthStencilState;
	// 深度和模板的使用类型
	FExclusiveDepthStencil::Type	DepthStencilAccess;

	// View的UniformBuffer
	FRHIUniformBuffer*				ViewUniformBuffer;
	FRHIUniformBuffer*				InstancedViewUniformBuffer;

	/** Will be bound as reflection capture uniform buffer in case where scene is not available, typically set to dummy/empty buffer to avoid null binding */
	FRHIUniformBuffer*				ReflectionCaptureUniformBuffer;
	// Pass的UniformBuffer
	FRHIUniformBuffer*				PassUniformBuffer;
	// 模板值
	uint32							StencilRef;
};

作为MeshPass的核心数据结构FMeshPassProcessor,其输入的FMeshBatch,输出是FMeshDrawCommands。

那么就有两个问题:

  1. 输入的FMeshBatch哪里来?
  2. 如何转换为FMeshDrawCommands?

2.1 FPrimitiveSceneProxy的创建

FMeshBatch是由FPrimitiveSceneProxy产生的。

FPrimitiveSceneProxy应该无须进行详细的介绍,其就是UPrimitiveComponent在渲染线程的代表。

让我们来看一下:FPrimitiveSceneProxy的创建。

对于场景中需要渲染的组件,会进行渲染状态的创建(所有的Mesh数据结构都会继承UPrimitiveComponent)。

渲染创建的创建,会调用UPrimitiveComponent::CreateRenderState_Concurrent

在其中会通知World中的Scene进行添加图元AddPrimitive。

GetWorld()->Scene->AddPrimitive(this);

FScene::AddPrimitive函数中会进行FPrimitiveSceneProxy和FPrimitiveSceneInfo的创建。

  • FPrimitiveSceneProxy和FPrimitiveSceneInfo是一对一的关系。
// Create the primitive's scene proxy.
FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->CreateSceneProxy();
Primitive->SceneProxy = PrimitiveSceneProxy;
if(!PrimitiveSceneProxy)
{
    // Primitives which don't have a proxy are irrelevant to the scene manager.
    return;
}

// Create the primitive scene info.
FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo(Primitive, this);
PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;

在创建完成之后,会通过ENQUEUE_RENDER_COMMAND在渲染线程调用AddPrimitiveSceneInfo_RenderThread。

代码如下:

ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
    [Params = MoveTemp(Params), Scene, PrimitiveSceneInfo, PreviousTransform = MoveTemp(PreviousTransform)](FRHICommandListImmediate& RHICmdList)
    {
        FPrimitiveSceneProxy* SceneProxy = Params.PrimitiveSceneProxy;
        FScopeCycleCounter Context(SceneProxy->GetStatId());
        SceneProxy->SetTransform(Params.RenderMatrix, Params.WorldBounds, Params.LocalBounds, Params.AttachmentRootPosition);

        // Create any RenderThreadResources required.
        SceneProxy->CreateRenderThreadResources();
		// 渲染线程添加图元
        Scene->AddPrimitiveSceneInfo_RenderThread(PrimitiveSceneInfo, PreviousTransform);
    });

FScene::AddPrimitiveSceneInfo_RenderThread 进行图元信息收集。

  • 添加到AddedPrimitiveSceneInfos中。
void FScene::AddPrimitiveSceneInfo_RenderThread(FPrimitiveSceneInfo* PrimitiveSceneInfo, const TOptional<FTransform>& PreviousTransform)
{
	check(IsInRenderingThread());
	check(PrimitiveSceneInfo->PackedIndex == INDEX_NONE);
	check(!AddedPrimitiveSceneInfos.Contains(PrimitiveSceneInfo));
    // 收集图元! 存储在AddedPrimitiveSceneInfos
	AddedPrimitiveSceneInfos.Add(PrimitiveSceneInfo);
    
	if (PreviousTransform.IsSet())
	{
		OverridenPreviousTransforms.Add(PrimitiveSceneInfo, PreviousTransform.GetValue().ToMatrixWithScale());
	}
}

2.2 FMeshBatch的收集

Mesh Drawing Pipeline 可知,Mesh Batches有两种:

  • Cached
  • Dynamic
2.2.1 Cached

给出Cached方式的调用流程:

FScene::UpdateAllPrimitiveSceneInfos
FPrimitiveSceneInfo::AddToScene
FPrimitiveSceneInfo::AddStaticMeshes
FPrimitiveSceneInfo::CacheMeshDrawCommands
FPrimitiveSceneProxy::DrawStaticElements

最后调用的DrawStaticElements 不正是 虚幻引擎之SecondPass支持 StaticMesh的SecondPass中添加核心代码的函数嘛!

DrawStaticElements 核心功能就是将材质和顶点数据等转换为FMeshBatch用于后续的渲染。

/**
	 * Draws the primitive's static elements.  This is called from the rendering thread once when the scene proxy is created.
	 * The static elements will only be rendered if GetViewRelevance declares static relevance.
	 * @param PDI - The interface which receives the primitive elements.
	 */
virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) {}

最后生成的FMeshBatch会被保存在FPrimitiveSceneInfo的StaticMeshes中。

/** The primitive's static meshes. */
TArray<class FStaticMeshBatch> StaticMeshes;

对于Cache的,每帧进行重用直到Proxy从场景中移除

2.2.2 Dynamic

给出Dynamic方式的调用流程:

FDeferredShadingSceneRenderer::Render
FDeferredShadingSceneRenderer::InitViews
FSceneRenderer::ComputeViewVisibility
FSceneRenderer::GatherDynamicMeshElements
FPrimitiveSceneProxy::GetDynamicMeshElements

GetDynamicMeshElements 负责收集动态的图元。

  • 正如FCableSceneProxy::GetDynamicMeshElements复写的那样。
	/** 
	 * Gathers the primitive's dynamic mesh elements.  This will only be called if GetViewRelevance declares dynamic relevance.
	 * This is called from the rendering thread for each set of views that might be rendered.  
	 * Game thread state like UObjects must have their properties mirrored on the proxy to avoid race conditions.  The rendering thread must not dereference UObjects.
	 * The gathered mesh elements will be used multiple times, any memory referenced must last as long as the Collector (eg no stack memory should be referenced).
	 * This function should not modify the proxy but simply collect a description of things to render.  Updates to the proxy need to be pushed from game thread or external events.
	 *
	 * @param Views - the array of views to consider.  These may not exist in the ViewFamily.
	 * @param ViewFamily - the view family, for convenience
	 * @param VisibilityMap - a bit representing this proxy's visibility in the Views array
	 * @param Collector - gathers the mesh elements to be rendered and provides mechanisms for temporary allocations
	 */
virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, class FMeshElementCollector& Collector) const {}
2.2.3 FMeshBatch的作用

上述的我们介绍了收集FMeshBatch的两种途径,但还没有仔细的看一下FMeshBatch到底包含了什么。它的作用是什么?

如下图所示:

在这里插入图片描述

FMeshBatch,记录了一组拥有相同材质和顶点工厂的FMeshBatchElement数据。

  • 一组FMeshBatchElement;

同一个FMeshBatch的所有FMeshBatchElement共享着相同的材质和顶点缓冲(可可被视为Vertex Factory)。

但通常情况(大多数情况)下,FMeshBatch只会有一个FMeshBatchElement。对于某些平台(不支持实例化),因此,Unreal改为将实例分成为许多批次进行绘制。这种情况下,FMeshBatchElement数组将用于存储其他元素。

  • 一個可以视为顶点缓冲区的顶点工厂,FVertexFactory
  • 一个代表代表材质的材质渲染代理,FMaterialRenderProxy

FMaterialRenderProxy是真正的着色器的三级结构!

它包含了一组FMaterial!它可以根据给定的RHI等级选择不同的材质!

然后,每个FMaterial都有一个FMaterialShaderMap,其中包含很多FShader!

在虚幻中,每个材质将被编译成为一组着色器,而不是一个。因为对于不同的平台和各种条件,虚幻将尝试提供最终优化的着色器,例如: 材质的静态开关将被编译为两个版本,而非if-else语句,FShader结构包含了像素着色器,顶点着色器等。

// Engine\Source\Runtime\Engine\Public\MeshBatch.h

// 网格批次元素, 存储了FMeshBatch单个网格所需的数据.
struct FMeshBatchElement
{
    // 网格的UniformBuffer, 如果使用GPU Scene, 则需要为null.
    FRHIUniformBuffer* PrimitiveUniformBuffer;
    // 网格的UniformBuffer在CPU侧的数据.
    const TUniformBuffer<FPrimitiveUniformShaderParameters>* PrimitiveUniformBufferResource;
    // 索引缓冲.
    const FIndexBuffer* IndexBuffer;

    union 
    {
        uint32* InstanceRuns;
        class FSplineMeshSceneProxy* SplineMeshSceneProxy;
    };
    // 用户数据.
    const void* UserData;
    void* VertexFactoryUserData;

    FRHIVertexBuffer* IndirectArgsBuffer;
    uint32 IndirectArgsOffset;

    // 图元ID模式, 有PrimID_FromPrimitiveSceneInfo(GPU Scene模式)和PrimID_DynamicPrimitiveShaderData(每个网格拥有自己的UniformBuffer)
    // 只可被渲染器修改.
    EPrimitiveIdMode PrimitiveIdMode : PrimID_NumBits + 1;
    uint32 DynamicPrimitiveShaderDataIndex : 24;

    uint32 FirstIndex;
    /** When 0, IndirectArgsBuffer will be used. */
    uint32 NumPrimitives;

    // Instance数量
    uint32 NumInstances;
    uint32 BaseVertexIndex;
    uint32 MinVertexIndex;
    uint32 MaxVertexIndex;
    int32 UserIndex;
    float MinScreenSize;
    float MaxScreenSize;

    uint32 InstancedLODIndex : 4;
    uint32 InstancedLODRange : 4;
    uint32 bUserDataIsColorVertexBuffer : 1;
    uint32 bIsSplineProxy : 1;
    uint32 bIsInstanceRuns : 1;

    // 获取图元数量.
    int32 GetNumPrimitives() const
    {
        if (bIsInstanceRuns && InstanceRuns)
        {
            int32 Count = 0;
            for (uint32 Run = 0; Run < NumInstances; Run++)
            {
                Count += NumPrimitives * (InstanceRuns[Run * 2 + 1] - InstanceRuns[Run * 2] + 1);
            }
            return Count;
        }
        else
        {
            return NumPrimitives * NumInstances;
        }
    }
};


// 网格批次.
struct FMeshBatch
{
    // 这组FMeshBatchElement的数据拥有相同的材质和顶点缓冲。
    // TInlineAllocator<1>表明Elements数组至少有1个元素.
    TArray<FMeshBatchElement,TInlineAllocator<1> > Elements; 
    const FVertexFactory* VertexFactory; // 顶点工厂.
    const FMaterialRenderProxy* MaterialRenderProxy; // 渲染所用的材质.

    uint16 MeshIdInPrimitive; // 图元所在的网格id, 用于相同图元的稳定排序.
    int8 LODIndex; // 网格LOD索引, 用于LOD的平滑过渡.
    uint8 SegmentIndex; // 子模型索引.
    
    // 裁剪标记.
    uint32 ReverseCulling : 1;
    uint32 bDisableBackfaceCulling : 1;

    // 特定渲染Pass的关联标记.
    uint32 CastShadow        : 1; // 是否在阴影Pass中渲染.
    uint32 bUseForMaterial    : 1; // 是否在需要材质的Pass中渲染.
    uint32 bUseForDepthPass : 1; // 是否在深度Pass中渲染.
    uint32 bUseAsOccluder    : 1; // 标明是否遮挡体.
    uint32 bWireframe        : 1; // 是否线框模式.

    uint32 Type : PT_NumBits; // 图元类型, 如PT_TriangleList(默认), PT_LineList, ...
    uint32 DepthPriorityGroup : SDPG_NumBits; // 深度优先级组, 如SDPG_World (default), SDPG_Foreground

    // 其它标记和数据
    const FLightCacheInterface* LCI;
    FHitProxyId BatchHitProxyId;
    float TessellationDisablingShadowMapMeshSize;
    
    uint32 bCanApplyViewModeOverrides : 1;
    uint32 bUseWireframeSelectionColoring : 1;
    uint32 bUseSelectionOutline : 1;
    uint32 bSelectable : 1;
    uint32 bRequiresPerElementVisibility : 1;
    uint32 bDitheredLODTransition : 1;
    uint32 bRenderToVirtualTexture : 1;
    uint32 RuntimeVirtualTextureMaterialType : RuntimeVirtualTexture::MaterialType_NumBits;
    
    (......)
    
    // 工具接口.
    bool IsTranslucent(ERHIFeatureLevel::Type InFeatureLevel) const;
    bool IsDecal(ERHIFeatureLevel::Type InFeatureLevel) const;
    bool IsDualBlend(ERHIFeatureLevel::Type InFeatureLevel) const;
    bool UseForHairStrands(ERHIFeatureLevel::Type InFeatureLevel) const;
    bool IsMasked(ERHIFeatureLevel::Type InFeatureLevel) const;
    int32 GetNumPrimitives() const;
    bool HasAnyDrawCalls() const;
};

FBatchMesh的作用就是:

  • FPrimitiveSceneProxy最终的渲染结构分离;
  • FMeshBatch包含了需要渲染的全部信息;
  • FPrimitiveSceneProxy不需要知道Pass将渲染什么;

2.3 FMeshDrawCommand的转换

2.3.1 为什么需要FMeshDrawCommand

FMeshBatch实现了渲染结构和FPrimitiveSceneProxy的分离。

FMeshBatch包含了完整的渲染所需的信息。

但是虚幻中含有大量的Pass,同样的FMeshBatch在不同的Pass下虽然有相同的部分,但是大多数情况并不相同。

即对于有些Pass而言,FMeshBatch含有一些冗余的信息,可以进一步进行划分。

FMeshDrawCommand类如下:

  • 只包含需要去绘制的数据
/** 
 * FMeshDrawCommand fully describes a mesh pass draw call, captured just above the RHI.  
		FMeshDrawCommand should contain only data needed to draw.  For InitViews payloads, use FVisibleMeshDrawCommand.
 * FMeshDrawCommands are cached at Primitive AddToScene time for vertex factories that support it (no per-frame or per-view shader binding changes).
 * Dynamic Instancing operates at the FMeshDrawCommand level for robustness.  
		Adding per-command shader bindings will reduce the efficiency of Dynamic Instancing, but rendering will always be correct.
 * Any resources referenced by a command must be kept alive for the lifetime of the command.  FMeshDrawCommand is not responsible for lifetime management of resources.
		For uniform buffers referenced by cached FMeshDrawCommand's, RHIUpdateUniformBuffer makes it possible to access per-frame data in the shader without changing bindings.
 */
class FMeshDrawCommand
{
public:
	
	/**
	 * Resource bindings
	 */
    // 资源绑定
	FMeshDrawShaderBindings ShaderBindings;
	FVertexInputStreamArray VertexStreams;
	FRHIIndexBuffer* IndexBuffer;

	/**
	 * PSO
	 */
    // 缓存的渲染管线状态
	FGraphicsMinimalPipelineStateId CachedPipelineId;

	/**
	 * Draw command parameters
	 */
    // 绘制命令的参数
	uint32 FirstIndex;
	uint32 NumPrimitives;
	uint32 NumInstances;

    // 顶点数据,包含普通模式和非直接模式
	union
	{
		struct 
		{
			uint32 BaseVertexIndex;
			uint32 NumVertices;
		} VertexParams;
		
		struct  
		{
			FRHIVertexBuffer* Buffer;
			uint32 Offset;
		} IndirectArgs;
	};

	int8 PrimitiveIdStreamIndex;

	/** Non-pipeline state */
    // 非渲染状态参数
	uint8 StencilRef;
    
    // ...省略部分源码
};

FMeshDrawCommand是一种更加紧凑、更加简洁、更有利于CPU访问的数据结构,用于表示场景数据。

FMeshDrawCommand类缓存了一个DrawCall所需要的最少的资源集合,包含Transform,Material,RenderState,顶点Buffer等。

  • VertexBuffer和IndexBuffer
  • ShaderBindings(Constant,SRV,UAV等)
  • PipelineStateObject(VS/PS/HS/GS/CS 和 RenderState)

这样有利于一些技术的实现:

  • 动态合并instance,也就是动态判断绘制的物体材质、VB等是否一样。一样的物体可以合并到一起绘制。
  • 一些新技术的需求
    • DXR技术;
    • GPU Driven Culling的技术;
    • Rendering Graph或者说可编程的渲染管线技术
2.3.2 如何创建FMeshDrawCommand

由上文可知,FMeshPassProcessor是建立FMeshDrawCommand的最重要的工具类。

FMeshPassProcessor最主要的作用就是生产FMeshDrawCommand。

在这里插入图片描述

由前文的EMeshPass::Type 枚举可知,UE中含有大量的MessPass,每个MeshPass都会对应一个FMeshPassProcessor。

FMeshPassProcessor(网格渲染Pass处理器),负责将场景中感兴趣的网格对象执行处理,将其由FMeshBatch对象转成一个或多个FMeshDrawCommand。

它的常见子类有:

  • FDepthPassMeshProcessor:深度通道网格处理器,对应EMeshPass::DepthPass

  • FBasePassMeshProcessor:几何通道网格处理器,对应EMeshPass::BasePass

  • FCustomDepthPassMeshProcessor:自定义深度通道网格处理器,对应EMeshPass::CustomDepth

  • FShadowDepthPassMeshProcessor:阴影通道网格处理器,对应EMeshPass::CSMShadowDepth

  • FTranslucencyDepthPassMeshProcessor:透明深度通道网格处理器,没有对应的EMeshPass

  • FLightmapDensityMeshProcessor:光照图网格处理器,对应EMeshPass::LightmapDensity

  • …等等

下面给出创建FMeshDrawCommand的执行流程图。

FSceneRenderer::SetupMeshPass
FParallelMeshDrawCommandPass::DispatchPassSetup
FMeshDrawCommandPassSetupTask::AnyThreadTask
GenerateDynamicMeshDrawCommands
FMeshPassProcessor::AddMeshBatch
FMeshPassProcessor::BuildMeshDrawCommands

FSceneRenderer::SetupMeshPass

收集完FMeshBatch后,渲染器会调用SetupMeshPass来创建FMeshDrawCommand。代码如下:

  • 遍历所有的MeshPass,创建对应类型的FMeshPassProcessor。
  • 接着调用DispatchPassSetup创建该FMeshPassProcessor对应的FMeshDrawCommand
void FSceneRenderer::SetupMeshPass(FViewInfo& View, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FViewCommands& ViewCommands)
{
	SCOPE_CYCLE_COUNTER(STAT_SetupMeshPass);

	const EShadingPath ShadingPath = Scene->GetShadingPath();

    // 遍历所有的MeshPass
	for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
	{
		const EMeshPass::Type PassType = (EMeshPass::Type)PassIndex;

		if ((FPassProcessorManager::GetPassFlags(ShadingPath, PassType) & EMeshPassFlags::MainView) != EMeshPassFlags::None)
		{
			// Mobile: BasePass and MobileBasePassCSM lists need to be merged and sorted after shadow pass.
			if (ShadingPath == EShadingPath::Mobile && (PassType == EMeshPass::BasePass || PassType == EMeshPass::MobileBasePassCSM))
			{
				continue;
			}

			if (ViewFamily.UseDebugViewPS() && ShadingPath == EShadingPath::Deferred)
			{
				switch (PassType)
				{
					case EMeshPass::DepthPass:
					case EMeshPass::CustomDepth:
					case EMeshPass::DebugViewMode:
#if WITH_EDITOR
					case EMeshPass::HitProxy:
					case EMeshPass::HitProxyOpaqueOnly:
#endif
					case EMeshPass::EditorSelection:

						break;
					default:
						continue;
				}
			}
			
            // 创建对应类型的FMeshPassProcessor
			PassProcessorCreateFunction CreateFunction = FPassProcessorManager::GetCreateFunction(ShadingPath, PassType);
			FMeshPassProcessor* MeshPassProcessor = CreateFunction(Scene, &View, nullptr);

			FParallelMeshDrawCommandPass& Pass = View.ParallelMeshDrawCommandPasses[PassIndex];

			if (ShouldDumpMeshDrawCommandInstancingStats())
			{
				Pass.SetDumpInstancingStats(GetMeshPassName(PassType));
			}

			Pass.DispatchPassSetup(
				Scene,
				View,
				PassType,
				BasePassDepthStencilAccess,
				MeshPassProcessor,
				View.DynamicMeshElements,
				&View.DynamicMeshElementsPassRelevance,
				View.NumVisibleDynamicMeshElements[PassType],
				ViewCommands.DynamicMeshCommandBuildRequests[PassType],
				ViewCommands.NumDynamicMeshCommandBuildRequestElements[PassType],
				ViewCommands.MeshCommands[PassIndex]);
		}
	}
}

FParallelMeshDrawCommandPass::DispatchPassSetup

对于每个类型的FMeshPassProcessor,调用这个函数。

  • 用输入的数据填充FMeshDrawCommandPassSetupTaskContext TaskContext。
  • 交换内存,就将收集的FMeshBatch传递下去。
  • 若是非并行方式,创建FMeshDrawCommandPassSetupTask,并调用AnyThreadTask执行创建任务
void FParallelMeshDrawCommandPass::DispatchPassSetup(
	FScene* Scene,
	const FViewInfo& View,
	EMeshPass::Type PassType,
	FExclusiveDepthStencil::Type BasePassDepthStencilAccess,
	FMeshPassProcessor* MeshPassProcessor,
	const TArray<FMeshBatchAndRelevance, SceneRenderingAllocator>& DynamicMeshElements,
	const TArray<FMeshPassMask, SceneRenderingAllocator>* DynamicMeshElementsPassRelevance,
	int32 NumDynamicMeshElements,
	TArray<const FStaticMeshBatch*, SceneRenderingAllocator>& InOutDynamicMeshCommandBuildRequests,
	int32 NumDynamicMeshCommandBuildRequestElements,
	FMeshCommandOneFrameArray& InOutMeshDrawCommands,
	FMeshPassProcessor* MobileBasePassCSMMeshPassProcessor,
	FMeshCommandOneFrameArray* InOutMobileBasePassCSMMeshDrawCommands
)
{
	TRACE_CPUPROFILER_EVENT_SCOPE(ParallelMdcDispatchPassSetup);
	check(!TaskEventRef.IsValid() && MeshPassProcessor != nullptr && TaskContext.PrimitiveIdBufferData == nullptr);
	check((PassType == EMeshPass::Num) == (DynamicMeshElementsPassRelevance == nullptr));

	MaxNumDraws = InOutMeshDrawCommands.Num() + NumDynamicMeshElements + NumDynamicMeshCommandBuildRequestElements;

    // 填充TaskContext
	TaskContext.MeshPassProcessor = MeshPassProcessor;
	TaskContext.MobileBasePassCSMMeshPassProcessor = MobileBasePassCSMMeshPassProcessor;
	TaskContext.DynamicMeshElements = &DynamicMeshElements;
	TaskContext.DynamicMeshElementsPassRelevance = DynamicMeshElementsPassRelevance;

	TaskContext.View = &View;
	TaskContext.ShadingPath = Scene->GetShadingPath();
	TaskContext.ShaderPlatform = Scene->GetShaderPlatform();
	TaskContext.PassType = PassType;
	TaskContext.bUseGPUScene = UseGPUScene(GMaxRHIShaderPlatform, View.GetFeatureLevel());
	TaskContext.bDynamicInstancing = IsDynamicInstancingEnabled(View.GetFeatureLevel());
	TaskContext.bReverseCulling = View.bReverseCulling;
	TaskContext.bRenderSceneTwoSided = View.bRenderSceneTwoSided;
	TaskContext.BasePassDepthStencilAccess = BasePassDepthStencilAccess;
	TaskContext.DefaultBasePassDepthStencilAccess = Scene->DefaultBasePassDepthStencilAccess;
	TaskContext.NumDynamicMeshElements = NumDynamicMeshElements;
	TaskContext.NumDynamicMeshCommandBuildRequestElements = NumDynamicMeshCommandBuildRequestElements;

	// Only apply instancing for ISR to main view passes
	const bool bIsMainViewPass = PassType != EMeshPass::Num && (FPassProcessorManager::GetPassFlags(TaskContext.ShadingPath, TaskContext.PassType) & EMeshPassFlags::MainView) != EMeshPassFlags::None;
	TaskContext.InstanceFactor = (bIsMainViewPass && View.IsInstancedStereoPass()) ? 2 : 1;

	// Setup translucency sort key update pass based on view.
    // 设置基于view的透明排序键
	TaskContext.TranslucencyPass = ETranslucencyPass::TPT_MAX;
	TaskContext.TranslucentSortPolicy = View.TranslucentSortPolicy;
	TaskContext.TranslucentSortAxis = View.TranslucentSortAxis;
	TaskContext.ViewOrigin = View.ViewMatrices.GetViewOrigin();
	TaskContext.ViewMatrix = View.ViewMatrices.GetViewMatrix();
	TaskContext.PrimitiveBounds = &Scene->PrimitiveBounds;

	switch (PassType)
	{
		case EMeshPass::TranslucencyStandard: TaskContext.TranslucencyPass = ETranslucencyPass::TPT_StandardTranslucency; break;
		case EMeshPass::TranslucencyAfterDOF: TaskContext.TranslucencyPass = ETranslucencyPass::TPT_TranslucencyAfterDOF; break;
		case EMeshPass::TranslucencyAfterDOFModulate: TaskContext.TranslucencyPass = ETranslucencyPass::TPT_TranslucencyAfterDOFModulate; break;
		case EMeshPass::TranslucencyAll: TaskContext.TranslucencyPass = ETranslucencyPass::TPT_AllTranslucency; break;
		case EMeshPass::MobileInverseOpacity: TaskContext.TranslucencyPass = ETranslucencyPass::TPT_StandardTranslucency; break;
	}

    // 交换命令列表
	FMemory::Memswap(&TaskContext.MeshDrawCommands, &InOutMeshDrawCommands, sizeof(InOutMeshDrawCommands));
	FMemory::Memswap(&TaskContext.DynamicMeshCommandBuildRequests, &InOutDynamicMeshCommandBuildRequests, sizeof(InOutDynamicMeshCommandBuildRequests));

	if (TaskContext.ShadingPath == EShadingPath::Mobile && TaskContext.PassType == EMeshPass::BasePass)
	{
		FMemory::Memswap(&TaskContext.MobileBasePassCSMMeshDrawCommands, InOutMobileBasePassCSMMeshDrawCommands, sizeof(*InOutMobileBasePassCSMMeshDrawCommands));
	}
	else
	{
		check(MobileBasePassCSMMeshPassProcessor == nullptr && InOutMobileBasePassCSMMeshDrawCommands == nullptr);
	}

	if (MaxNumDraws > 0)
	{
		// Preallocate resources on rendering thread based on MaxNumDraws.
        // 根据最大绘制数量(MaxNumDraws)在渲染线程预分配资源
		bPrimitiveIdBufferDataOwnedByRHIThread = false;
		TaskContext.PrimitiveIdBufferDataSize = TaskContext.InstanceFactor * MaxNumDraws * sizeof(int32);
		TaskContext.PrimitiveIdBufferData = FMemory::Malloc(TaskContext.PrimitiveIdBufferDataSize);
		PrimitiveIdVertexBufferPoolEntry = GPrimitiveIdVertexBufferPool.Allocate(TaskContext.PrimitiveIdBufferDataSize);
		TaskContext.MeshDrawCommands.Reserve(MaxNumDraws);
		TaskContext.TempVisibleMeshDrawCommands.Reserve(MaxNumDraws);

		const bool bExecuteInParallel = FApp::ShouldUseThreadingForPerformance()
			&& CVarMeshDrawCommandsParallelPassSetup.GetValueOnRenderThread() > 0
			&& GIsThreadedRendering; // Rendering thread is required to safely use rendering resources in parallel.
	
        // 如果是并行方式, 便创建并行任务实例并加入TaskGraph系统执行.
		if (bExecuteInParallel)
		{
			if (GAllowOnDemandShaderCreation && RHISupportsMultithreadedShaderCreation(GMaxRHIShaderPlatform))
			{
				TaskEventRef = TGraphTask<FMeshDrawCommandPassSetupTask>::CreateTask(nullptr, ENamedThreads::GetRenderThread()).ConstructAndDispatchWhenReady(TaskContext);
			}
			else
			{
				FGraphEventArray DependentGraphEvents;
				DependentGraphEvents.Add(TGraphTask<FMeshDrawCommandPassSetupTask>::CreateTask(nullptr, ENamedThreads::GetRenderThread()).ConstructAndDispatchWhenReady(TaskContext));
				TaskEventRef = TGraphTask<FMeshDrawCommandInitResourcesTask>::CreateTask(&DependentGraphEvents, ENamedThreads::GetRenderThread()).ConstructAndDispatchWhenReady(TaskContext);
			}
		}
		else
		{
            // 非并行的话,创建FMeshDrawCommandPassSetupTask,并调用AnyThreadTask执行创建任务
			QUICK_SCOPE_CYCLE_COUNTER(STAT_MeshPassSetupImmediate);
			FMeshDrawCommandPassSetupTask Task(TaskContext);
			Task.AnyThreadTask();
			if (!GAllowOnDemandShaderCreation || !RHISupportsMultithreadedShaderCreation(GMaxRHIShaderPlatform))
			{
				FMeshDrawCommandInitResourcesTask DependentTask(TaskContext);
				DependentTask.AnyThreadTask();
			}
		}
	}
}

FMeshDrawCommandPassSetupTask::AnyThreadTask

FMeshDrawCommandPassSetupTask是用来并行创建mesh draw commands的。包含了命令的生成,排序,合并等。

/**
 * Task for a parallel setup of mesh draw commands. Includes generation of dynamic mesh draw commands, sorting, merging etc.
 */
class FMeshDrawCommandPassSetupTask
{
    // ...省略部分源码
}

让我们来一起看下void AnyThreadTask() 函数:

void AnyThreadTask()
{
    TRACE_CPUPROFILER_EVENT_SCOPE(MeshDrawCommandPassSetupTask);
    // ... 省略 Mobile base pass的分支

    // 生成DrawCommand
    GenerateDynamicMeshDrawCommands(
        *Context.View,
        Context.ShadingPath,
        Context.PassType,
        Context.MeshPassProcessor,
        *Context.DynamicMeshElements,
        Context.DynamicMeshElementsPassRelevance,
        Context.NumDynamicMeshElements,
        Context.DynamicMeshCommandBuildRequests,
        Context.NumDynamicMeshCommandBuildRequestElements,
        Context.MeshDrawCommands,
        Context.MeshDrawCommandStorage,
        Context.MinimalPipelineStatePassSet,
        Context.NeedsShaderInitialisation
    );

    // ... 省略部分源码
}

GenerateDynamicMeshDrawCommands

  • 对于指定的某个类型的MessPass,将收集的FMeshBatch转化为FMeshDrawCommands。
  • 在该函数中,会分别处理动态的MeshBatch和静态的MeshBatch。
  • 通过调用FMeshPassProcessor的AddMeshBatch将其转换为FMeshDrawCommands。
/**
 * Converts each FMeshBatch into a set of FMeshDrawCommands for a specific mesh pass type.
 */
void GenerateDynamicMeshDrawCommands(
	const FViewInfo& View,
	EShadingPath ShadingPath,
	EMeshPass::Type PassType,
	FMeshPassProcessor* PassMeshProcessor,
	const TArray<FMeshBatchAndRelevance, SceneRenderingAllocator>& DynamicMeshElements,
	const TArray<FMeshPassMask, SceneRenderingAllocator>* DynamicMeshElementsPassRelevance,
	int32 MaxNumDynamicMeshElements,
	const TArray<const FStaticMeshBatch*, SceneRenderingAllocator>& DynamicMeshCommandBuildRequests,
	int32 MaxNumBuildRequestElements,
	FMeshCommandOneFrameArray& VisibleCommands,
	FDynamicMeshDrawCommandStorage& MeshDrawCommandStorage,
	FGraphicsMinimalPipelineStateSet& MinimalPipelineStatePassSet,
	bool& NeedsShaderInitialisation
)
{
	QUICK_SCOPE_CYCLE_COUNTER(STAT_GenerateDynamicMeshDrawCommands);
	check(PassMeshProcessor);
	check((PassType == EMeshPass::Num) == (DynamicMeshElementsPassRelevance == nullptr));

    // 
	FDynamicPassMeshDrawListContext DynamicPassMeshDrawListContext(
		MeshDrawCommandStorage,
		VisibleCommands,
		MinimalPipelineStatePassSet,
		NeedsShaderInitialisation
	);
	PassMeshProcessor->SetDrawListContext(&DynamicPassMeshDrawListContext);

	{
		const int32 NumCommandsBefore = VisibleCommands.Num();
		const int32 NumDynamicMeshBatches = DynamicMeshElements.Num();
		
        // 处理所有动态的FMeshBatch!
		for (int32 MeshIndex = 0; MeshIndex < NumDynamicMeshBatches; MeshIndex++)
		{
			if (!DynamicMeshElementsPassRelevance || (*DynamicMeshElementsPassRelevance)[MeshIndex].Get(PassType))
			{
				const FMeshBatchAndRelevance& MeshAndRelevance = DynamicMeshElements[MeshIndex];
				const uint64 BatchElementMask = ~0ull;

				PassMeshProcessor->AddMeshBatch(*MeshAndRelevance.Mesh, BatchElementMask, MeshAndRelevance.PrimitiveSceneProxy);
			}
		}

		const int32 NumCommandsGenerated = VisibleCommands.Num() - NumCommandsBefore;
		checkf(NumCommandsGenerated <= MaxNumDynamicMeshElements,
			TEXT("Generated %d mesh draw commands for DynamicMeshElements, while preallocating resources only for %d of them."), NumCommandsGenerated, MaxNumDynamicMeshElements);
	}

	{
		const int32 NumCommandsBefore = VisibleCommands.Num();
		const int32 NumStaticMeshBatches = DynamicMeshCommandBuildRequests.Num();

        // 处理所有静态的FMeshBatch!
		for (int32 MeshIndex = 0; MeshIndex < NumStaticMeshBatches; MeshIndex++)
		{
			const FStaticMeshBatch* StaticMeshBatch = DynamicMeshCommandBuildRequests[MeshIndex];
			const uint64 DefaultBatchElementMask = ~0ul;
			PassMeshProcessor->AddMeshBatch(*StaticMeshBatch, DefaultBatchElementMask, StaticMeshBatch->PrimitiveSceneInfo->Proxy, StaticMeshBatch->Id);
		}

		const int32 NumCommandsGenerated = VisibleCommands.Num() - NumCommandsBefore;
		checkf(NumCommandsGenerated <= MaxNumBuildRequestElements,
			TEXT("Generated %d mesh draw commands for DynamicMeshCommandBuildRequests, while preallocating resources only for %d of them."), NumCommandsGenerated, MaxNumBuildRequestElements);
	}
}

FMeshPassProcessor::AddMeshBatch

FMeshPassProcessor::AddMeshBatch由子类实现。不同的子类实现的内容不同。

下面以FBasePassMeshProcessor为例进行介绍,其代码如下。

void FBasePassMeshProcessor::AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId)
{
    // 判断是否使用材质
	if (MeshBatch.bUseForMaterial)
	{
		// Determine the mesh's material and blend mode.
        // 确定材质和混合模式
		const FMaterialRenderProxy* FallbackMaterialRenderProxyPtr = nullptr;
		const FMaterial& Material = MeshBatch.MaterialRenderProxy->GetMaterialWithFallback(FeatureLevel, FallbackMaterialRenderProxyPtr);

		const FMaterialRenderProxy& MaterialRenderProxy = FallbackMaterialRenderProxyPtr ? *FallbackMaterialRenderProxyPtr : *MeshBatch.MaterialRenderProxy;

		const EBlendMode BlendMode = Material.GetBlendMode();
		const FMaterialShadingModelField ShadingModels = Material.GetShadingModels();
		const bool bIsTranslucent = IsTranslucentBlendMode(BlendMode);
		const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch);
		const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(MeshBatch, Material, OverrideSettings);
		const ERasterizerCullMode MeshCullMode = ComputeMeshCullMode(MeshBatch, Material, OverrideSettings);


		bool bShouldDraw = false;
		
        // 是否为半透明
		if (bTranslucentBasePass)
		{
			if (bIsTranslucent && !Material.IsDeferredDecal())
			{
				switch (TranslucencyPassType)
				{
				case ETranslucencyPass::TPT_StandardTranslucency:
					bShouldDraw = !Material.IsTranslucencyAfterDOFEnabled();
					break;

				case ETranslucencyPass::TPT_TranslucencyAfterDOF:
					bShouldDraw = Material.IsTranslucencyAfterDOFEnabled();
					break;

				// only dual blended or modulate surfaces need background modulation
				case ETranslucencyPass::TPT_TranslucencyAfterDOFModulate:
					bShouldDraw = Material.IsTranslucencyAfterDOFEnabled() && (Material.IsDualBlendingEnabled(GetFeatureLevelShaderPlatform(FeatureLevel)) || BlendMode == BLEND_Modulate);
					break;

				case ETranslucencyPass::TPT_AllTranslucency:
					bShouldDraw = true;
					break;
				}
			}
		}
		else
		{
			bShouldDraw = !bIsTranslucent;
		}


		// Only draw opaque materials.
        // 仅绘制不透明的材质
		if (bShouldDraw
			&& (!PrimitiveSceneProxy || PrimitiveSceneProxy->ShouldRenderInMainPass())
			&& ShouldIncludeDomainInMeshPass(Material.GetMaterialDomain())
			&& ShouldIncludeMaterialInDefaultOpaquePass(Material))
		{
			// Check for a cached light-map.
			const bool bIsLitMaterial = ShadingModels.IsLit();
			static const auto AllowStaticLightingVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowStaticLighting"));
			const bool bAllowStaticLighting = (!AllowStaticLightingVar || AllowStaticLightingVar->GetValueOnRenderThread() != 0);

			const FLightMapInteraction LightMapInteraction = (bAllowStaticLighting && MeshBatch.LCI && bIsLitMaterial)
				? MeshBatch.LCI->GetLightMapInteraction(FeatureLevel)
				: FLightMapInteraction();

			// force LQ lightmaps based on system settings
			const bool bPlatformAllowsHighQualityLightMaps = AllowHighQualityLightmaps(FeatureLevel);
			const bool bAllowHighQualityLightMaps = bPlatformAllowsHighQualityLightMaps && LightMapInteraction.AllowsHighQualityLightmaps();

			const bool bAllowIndirectLightingCache = Scene && Scene->PrecomputedLightVolumes.Num() > 0;
			const bool bUseVolumetricLightmap = Scene && Scene->VolumetricLightmapSceneData.HasData();

			FMeshMaterialShaderElementData MeshMaterialShaderElementData;
			MeshMaterialShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, StaticMeshId, true);
		
            // 处理简单的前向渲染
			if (IsSimpleForwardShadingEnabled(GetFeatureLevelShaderPlatform(FeatureLevel)))
			{
				// Only compiling simple lighting shaders for HQ lightmaps to save on permutations
				check(bPlatformAllowsHighQualityLightMaps);
				AddMeshBatchForSimpleForwardShading(
					MeshBatch,
					BatchElementMask,
					StaticMeshId,
					PrimitiveSceneProxy,
					MaterialRenderProxy,
					Material,
					LightMapInteraction,
					bIsLitMaterial,
					bAllowStaticLighting,
					bUseVolumetricLightmap,
					bAllowIndirectLightingCache,
					MeshFillMode,
					MeshCullMode);
			}
			// Render volumetric translucent self-shadowing only for >= SM4 and fallback to non-shadowed for lesser shader models
            // 渲染体积透明自阴影的物体
			else if (bIsLitMaterial
				&& bIsTranslucent
				&& PrimitiveSceneProxy
				&& PrimitiveSceneProxy->CastsVolumetricTranslucentShadow())
			{
				checkSlow(ViewIfDynamicMeshCommand && ViewIfDynamicMeshCommand->bIsViewInfo);
				const FViewInfo* ViewInfo = (FViewInfo*)ViewIfDynamicMeshCommand;

				const int32 PrimitiveIndex = PrimitiveSceneProxy->GetPrimitiveSceneInfo()->GetIndex();

				const FUniformBufferRHIRef* UniformBufferPtr = ViewInfo->TranslucentSelfShadowUniformBufferMap.Find(PrimitiveIndex);

				FSelfShadowLightCacheElementData ElementData;
				ElementData.LCI = MeshBatch.LCI;
				ElementData.SelfShadowTranslucencyUniformBuffer = UniformBufferPtr ? (*UniformBufferPtr).GetReference() : GEmptyTranslucentSelfShadowUniformBuffer.GetUniformBufferRHI();

				if (bIsLitMaterial
					&& bAllowStaticLighting
					&& bUseVolumetricLightmap
					&& PrimitiveSceneProxy)
				{
					Process< FSelfShadowedVolumetricLightmapPolicy >(
						MeshBatch,
						BatchElementMask,
						StaticMeshId,
						PrimitiveSceneProxy,
						MaterialRenderProxy,
						Material,
						BlendMode,
						ShadingModels,
						FSelfShadowedVolumetricLightmapPolicy(),
						ElementData,
						MeshFillMode,
						MeshCullMode);
				}
				else if (IsIndirectLightingCacheAllowed(FeatureLevel)
					&& bAllowIndirectLightingCache
					&& PrimitiveSceneProxy)
				{
					// Apply cached point indirect lighting as well as self shadowing if needed
					Process< FSelfShadowedCachedPointIndirectLightingPolicy >(
						MeshBatch,
						BatchElementMask,
						StaticMeshId,
						PrimitiveSceneProxy,
						MaterialRenderProxy,
						Material,
						BlendMode,
						ShadingModels,
						FSelfShadowedCachedPointIndirectLightingPolicy(),
						ElementData,
						MeshFillMode,
						MeshCullMode);
				}
				else
				{
					Process< FSelfShadowedTranslucencyPolicy >(
						MeshBatch,
						BatchElementMask,
						StaticMeshId,
						PrimitiveSceneProxy,
						MaterialRenderProxy,
						Material,
						BlendMode,
						ShadingModels,
						FSelfShadowedTranslucencyPolicy(),
						ElementData.SelfShadowTranslucencyUniformBuffer,
						MeshFillMode,
						MeshCullMode);
				}
			}
            // 根据不同的光照图的选项和质量等级,调用Process进行处理
			else
			{
				static const auto CVarSupportLowQualityLightmap = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.SupportLowQualityLightmaps"));
				const bool bAllowLowQualityLightMaps = (!CVarSupportLowQualityLightmap) || (CVarSupportLowQualityLightmap->GetValueOnAnyThread() != 0);

				switch (LightMapInteraction.GetType())
				{
				case LMIT_Texture:
					if (bAllowHighQualityLightMaps)
					{
						const FShadowMapInteraction ShadowMapInteraction = (bAllowStaticLighting && MeshBatch.LCI && bIsLitMaterial)
							? MeshBatch.LCI->GetShadowMapInteraction(FeatureLevel)
							: FShadowMapInteraction();

						if (ShadowMapInteraction.GetType() == SMIT_Texture)
						{
							Process< FUniformLightMapPolicy >(
								MeshBatch,
								BatchElementMask,
								StaticMeshId,
								PrimitiveSceneProxy,
								MaterialRenderProxy,
								Material,
								BlendMode,
								ShadingModels,
								FUniformLightMapPolicy(LMP_DISTANCE_FIELD_SHADOWS_AND_HQ_LIGHTMAP),
								MeshBatch.LCI,
								MeshFillMode,
								MeshCullMode);
						}
						else
						{
							Process< FUniformLightMapPolicy >(
								MeshBatch,
								BatchElementMask,
								StaticMeshId,
								PrimitiveSceneProxy,
								MaterialRenderProxy,
								Material,
								BlendMode,
								ShadingModels,
								FUniformLightMapPolicy(LMP_HQ_LIGHTMAP),
								MeshBatch.LCI,
								MeshFillMode,
								MeshCullMode);
						}
					}
					else if (bAllowLowQualityLightMaps)
					{
						Process< FUniformLightMapPolicy >(
							MeshBatch,
							BatchElementMask,
							StaticMeshId,
							PrimitiveSceneProxy,
							MaterialRenderProxy,
							Material,
							BlendMode,
							ShadingModels,
							FUniformLightMapPolicy(LMP_LQ_LIGHTMAP),
							MeshBatch.LCI,
							MeshFillMode,
							MeshCullMode);
					}
					else
					{
						Process< FUniformLightMapPolicy >(
							MeshBatch,
							BatchElementMask,
							StaticMeshId,
							PrimitiveSceneProxy,
							MaterialRenderProxy,
							Material,
							BlendMode,
							ShadingModels,
							FUniformLightMapPolicy(LMP_NO_LIGHTMAP),
							MeshBatch.LCI,
							MeshFillMode,
							MeshCullMode);
					}
					break;
				default:
					if (bIsLitMaterial
						&& bAllowStaticLighting
						&& Scene
						&& Scene->VolumetricLightmapSceneData.HasData()
						&& PrimitiveSceneProxy
						&& (PrimitiveSceneProxy->IsMovable()
							|| PrimitiveSceneProxy->NeedsUnbuiltPreviewLighting()
							|| PrimitiveSceneProxy->GetLightmapType() == ELightmapType::ForceVolumetric))
					{
						Process< FUniformLightMapPolicy >(
							MeshBatch,
							BatchElementMask,
							StaticMeshId,
							PrimitiveSceneProxy,
							MaterialRenderProxy,
							Material,
							BlendMode,
							ShadingModels,
							FUniformLightMapPolicy(LMP_PRECOMPUTED_IRRADIANCE_VOLUME_INDIRECT_LIGHTING),
							MeshBatch.LCI,
							MeshFillMode,
							MeshCullMode);
					}
					else if (bIsLitMaterial
						&& IsIndirectLightingCacheAllowed(FeatureLevel)
						&& Scene
						&& Scene->PrecomputedLightVolumes.Num() > 0
						&& PrimitiveSceneProxy)
					{
						const FIndirectLightingCacheAllocation* IndirectLightingCacheAllocation = PrimitiveSceneProxy->GetPrimitiveSceneInfo()->IndirectLightingCacheAllocation;
						const bool bPrimitiveIsMovable = PrimitiveSceneProxy->IsMovable();
						const bool bPrimitiveUsesILC = PrimitiveSceneProxy->GetIndirectLightingCacheQuality() != ILCQ_Off;

						// Use the indirect lighting cache shaders if the object has a cache allocation
						// This happens for objects with unbuilt lighting
						if (bPrimitiveUsesILC &&
							((IndirectLightingCacheAllocation && IndirectLightingCacheAllocation->IsValid())
								// Use the indirect lighting cache shaders if the object is movable, it may not have a cache allocation yet because that is done in InitViews
								// And movable objects are sometimes rendered in the static draw lists
								|| bPrimitiveIsMovable))
						{
							if (CanIndirectLightingCacheUseVolumeTexture(FeatureLevel)
								// Translucency forces point sample for pixel performance
								&& !bIsTranslucent
								&& ((IndirectLightingCacheAllocation && !IndirectLightingCacheAllocation->bPointSample)
									|| (bPrimitiveIsMovable && PrimitiveSceneProxy->GetIndirectLightingCacheQuality() == ILCQ_Volume)))
							{
								// Use a lightmap policy that supports reading indirect lighting from a volume texture for dynamic objects
								Process< FUniformLightMapPolicy >(
									MeshBatch,
									BatchElementMask,
									StaticMeshId,
									PrimitiveSceneProxy,
									MaterialRenderProxy,
									Material,
									BlendMode,
									ShadingModels,
									FUniformLightMapPolicy(LMP_CACHED_VOLUME_INDIRECT_LIGHTING),
									MeshBatch.LCI,
									MeshFillMode,
									MeshCullMode);
							}
							else
							{
								// Use a lightmap policy that supports reading indirect lighting from a single SH sample
								Process< FUniformLightMapPolicy >(
									MeshBatch,
									BatchElementMask,
									StaticMeshId,
									PrimitiveSceneProxy,
									MaterialRenderProxy,
									Material,
									BlendMode,
									ShadingModels,
									FUniformLightMapPolicy(LMP_CACHED_POINT_INDIRECT_LIGHTING),
									MeshBatch.LCI,
									MeshFillMode,
									MeshCullMode);
							}
						}
						else
						{
							Process< FUniformLightMapPolicy >(
								MeshBatch,
								BatchElementMask,
								StaticMeshId,
								PrimitiveSceneProxy,
								MaterialRenderProxy,
								Material,
								BlendMode,
								ShadingModels,
								FUniformLightMapPolicy(LMP_NO_LIGHTMAP),
								MeshBatch.LCI,
								MeshFillMode,
								MeshCullMode);
						}
					}
					else
					{
						Process< FUniformLightMapPolicy >(
							MeshBatch,
							BatchElementMask,
							StaticMeshId,
							PrimitiveSceneProxy,
							MaterialRenderProxy,
							Material,
							BlendMode,
							ShadingModels,
							FUniformLightMapPolicy(LMP_NO_LIGHTMAP),
							MeshBatch.LCI,
							MeshFillMode,
							MeshCullMode);
					}
					break;
				};
			}
		}
	}
}

那么,我们还需要看一下Process函数:

template<typename LightMapPolicyType>
void FBasePassMeshProcessor::Process(
	const FMeshBatch& RESTRICT MeshBatch,
	uint64 BatchElementMask,
	int32 StaticMeshId,
	const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
	const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
	const FMaterial& RESTRICT MaterialResource,
	EBlendMode BlendMode,
	FMaterialShadingModelField ShadingModels,
	const LightMapPolicyType& RESTRICT LightMapPolicy,
	const typename LightMapPolicyType::ElementDataType& RESTRICT LightMapElementData,
	ERasterizerFillMode MeshFillMode,
	ERasterizerCullMode MeshCullMode)
{
    // 顶点工厂
	const FVertexFactory* VertexFactory = MeshBatch.VertexFactory;

	const bool bRenderSkylight = Scene && Scene->ShouldRenderSkylightInBasePass(BlendMode) && ShadingModels.IsLit();
	const bool bRenderAtmosphericFog = IsTranslucentBlendMode(BlendMode) && (Scene && Scene->HasAtmosphericFog() && Scene->ReadOnlyCVARCache.bEnableAtmosphericFog);

	TMeshProcessorShaders<
		TBasePassVertexShaderPolicyParamType<LightMapPolicyType>,
		FBaseHS,
		FBaseDS,
		TBasePassPixelShaderPolicyParamType<LightMapPolicyType>> BasePassShaders;

    // 获取指定光照图策略类型的shader!
	GetBasePassShaders<LightMapPolicyType>(
		MaterialResource,
		VertexFactory->GetType(),
		LightMapPolicy,
		FeatureLevel,
		bRenderAtmosphericFog,
		bRenderSkylight,
		Get128BitRequirement(),
		BasePassShaders.HullShader,
		BasePassShaders.DomainShader,
		BasePassShaders.VertexShader,
		BasePassShaders.PixelShader
		);
	
    // 渲染状态的设置与处理
	FMeshPassProcessorRenderState DrawRenderState(PassDrawRenderState);

	SetDepthStencilStateForBasePass(
		ViewIfDynamicMeshCommand,
		DrawRenderState,
		FeatureLevel,
		MeshBatch,
		StaticMeshId,
		PrimitiveSceneProxy,
		bEnableReceiveDecalOutput);

	if (bTranslucentBasePass)
	{
		SetTranslucentRenderState(DrawRenderState, MaterialResource, GShaderPlatformForFeatureLevel[FeatureLevel], TranslucencyPassType);
	}

	TBasePassShaderElementData<LightMapPolicyType> ShaderElementData(LightMapElementData);
	ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, StaticMeshId, true);

    // 排序键值
	FMeshDrawCommandSortKey SortKey = CalculateBasePassMeshStaticSortKey(EarlyZPassMode, BlendMode, BasePassShaders.VertexShader.GetShader(), BasePassShaders.PixelShader.GetShader());
	
    // 调用BuildMeshDrawCommands将MeshBatch转为FMeshDrawCommands
	BuildMeshDrawCommands(
		MeshBatch,
		BatchElementMask,
		PrimitiveSceneProxy,
		MaterialRenderProxy,
		MaterialResource,
		DrawRenderState,
		BasePassShaders,
		MeshFillMode,
		MeshCullMode,
		SortKey,
		EMeshPassFeatures::Default,
		ShaderElementData);
}

FMeshPassProcessor::BuildMeshDrawCommands

BuildMeshDrawCommands这个函数为所有FMeshPassProcessor通用的,将1个FMeshBatch转换成1或多个MeshDrawCommands。

只要做了几件事情:

  • 创建一个FMeshDrawCommand;
  • 设置着色器、渲染管线状态、模板值Ref等;
  • 遍历MeshBatch下所有Element,
template<typename PassShadersType, typename ShaderElementDataType>
void FMeshPassProcessor::BuildMeshDrawCommands(
	const FMeshBatch& RESTRICT MeshBatch,
	uint64 BatchElementMask,
	const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
	const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
	const FMaterial& RESTRICT MaterialResource,
	const FMeshPassProcessorRenderState& RESTRICT DrawRenderState,
	PassShadersType PassShaders,
	ERasterizerFillMode MeshFillMode,
	ERasterizerCullMode MeshCullMode,
	FMeshDrawCommandSortKey SortKey,
	EMeshPassFeatures MeshPassFeatures,
	const ShaderElementDataType& ShaderElementData)
{
	const FVertexFactory* RESTRICT VertexFactory = MeshBatch.VertexFactory;
	const FPrimitiveSceneInfo* RESTRICT PrimitiveSceneInfo = PrimitiveSceneProxy ? PrimitiveSceneProxy->GetPrimitiveSceneInfo() : nullptr;

    // FMeshDrawCommand实例, 用于收集各类渲染资源和数据
	FMeshDrawCommand SharedMeshDrawCommand;

	SharedMeshDrawCommand.SetStencilRef(DrawRenderState.GetStencilRef());

    // 渲染状态实例
	FGraphicsMinimalPipelineStateInitializer PipelineState;
	PipelineState.PrimitiveType = (EPrimitiveType)MeshBatch.Type;
	PipelineState.ImmutableSamplerState = MaterialRenderProxy.ImmutableSamplerState;

	EVertexInputStreamType InputStreamType = EVertexInputStreamType::Default;
	if ((MeshPassFeatures & EMeshPassFeatures::PositionOnly) != EMeshPassFeatures::Default)				InputStreamType = EVertexInputStreamType::PositionOnly;
	if ((MeshPassFeatures & EMeshPassFeatures::PositionAndNormalOnly) != EMeshPassFeatures::Default)	InputStreamType = EVertexInputStreamType::PositionAndNormalOnly;

	check(VertexFactory && VertexFactory->IsInitialized());
	FRHIVertexDeclaration* VertexDeclaration = VertexFactory->GetDeclaration(InputStreamType);

	check(!VertexFactory->NeedsDeclaration() || VertexDeclaration);

	SharedMeshDrawCommand.SetShaders(VertexDeclaration, PassShaders.GetUntypedShaders(), PipelineState);

	PipelineState.RasterizerState = GetStaticRasterizerState<true>(MeshFillMode, MeshCullMode);

	check(DrawRenderState.GetDepthStencilState());
	check(DrawRenderState.GetBlendState());

	PipelineState.BlendState = DrawRenderState.GetBlendState();
	PipelineState.DepthStencilState = DrawRenderState.GetDepthStencilState();
	PipelineState.DrawShadingRate = GetShadingRateFromMaterial(MaterialResource.GetShadingRate());

	check(VertexFactory && VertexFactory->IsInitialized());
	VertexFactory->GetStreams(FeatureLevel, InputStreamType, SharedMeshDrawCommand.VertexStreams);

	SharedMeshDrawCommand.PrimitiveIdStreamIndex = VertexFactory->GetPrimitiveIdStreamIndex(InputStreamType);

    // 处理VS/PS/GS等shader的绑定数据
	int32 DataOffset = 0;
	if (PassShaders.VertexShader.IsValid())
	{
		FMeshDrawSingleShaderBindings ShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Vertex, DataOffset);
		PassShaders.VertexShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, ShaderBindings);
	}

	if (PassShaders.HullShader.IsValid() & PassShaders.DomainShader.IsValid())
	{
		FMeshDrawSingleShaderBindings HullShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Hull, DataOffset);
		FMeshDrawSingleShaderBindings DomainShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Domain, DataOffset);
		PassShaders.HullShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, HullShaderBindings);
		PassShaders.DomainShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, DomainShaderBindings);
	}

	if (PassShaders.PixelShader.IsValid())
	{
		FMeshDrawSingleShaderBindings ShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Pixel, DataOffset);
		PassShaders.PixelShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, ShaderBindings);
	}

	if (PassShaders.GeometryShader.IsValid())
	{
		FMeshDrawSingleShaderBindings ShaderBindings = SharedMeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Geometry, DataOffset);
		PassShaders.GeometryShader->GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, MaterialResource, DrawRenderState, ShaderElementData, ShaderBindings);
	}

	SharedMeshDrawCommand.SetDebugData(PrimitiveSceneProxy, &MaterialResource, &MaterialRenderProxy, PassShaders.GetUntypedShaders(), VertexFactory);

    // 遍历该FMeshBatch的所有MeshBatchElement, 从材质中获取FMeshBatchElement关联的所有shader类型的绑定数据
	const int32 NumElements = MeshBatch.Elements.Num();

	for (int32 BatchElementIndex = 0; BatchElementIndex < NumElements; BatchElementIndex++)
	{
		if ((1ull << BatchElementIndex) & BatchElementMask)
		{
			const FMeshBatchElement& BatchElement = MeshBatch.Elements[BatchElementIndex];
			FMeshDrawCommand& MeshDrawCommand = DrawListContext->AddCommand(SharedMeshDrawCommand, NumElements);

			DataOffset = 0;
			if (PassShaders.VertexShader.IsValid())
			{
				FMeshDrawSingleShaderBindings VertexShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Vertex, DataOffset);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.VertexShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, InputStreamType, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, VertexShaderBindings, MeshDrawCommand.VertexStreams);
			}

			if (PassShaders.HullShader.IsValid() & PassShaders.DomainShader.IsValid())
			{
				FMeshDrawSingleShaderBindings HullShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Hull, DataOffset);
				FMeshDrawSingleShaderBindings DomainShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Domain, DataOffset);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.HullShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, EVertexInputStreamType::Default, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, HullShaderBindings, MeshDrawCommand.VertexStreams);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.DomainShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, EVertexInputStreamType::Default, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, DomainShaderBindings, MeshDrawCommand.VertexStreams);
			}

			if (PassShaders.PixelShader.IsValid())
			{
				FMeshDrawSingleShaderBindings PixelShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Pixel, DataOffset);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.PixelShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, EVertexInputStreamType::Default, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, PixelShaderBindings, MeshDrawCommand.VertexStreams);
			}


			if (PassShaders.GeometryShader.IsValid())
			{
				FMeshDrawSingleShaderBindings GeometryShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(SF_Geometry, DataOffset);
				FMeshMaterialShader::GetElementShaderBindings(PassShaders.GeometryShader, Scene, ViewIfDynamicMeshCommand, VertexFactory, EVertexInputStreamType::Default, FeatureLevel, PrimitiveSceneProxy, MeshBatch, BatchElement, ShaderElementData, GeometryShaderBindings, MeshDrawCommand.VertexStreams);
			}
			
            // 处理并获得PrimitiveId
			int32 DrawPrimitiveId;
			int32 ScenePrimitiveId;
			GetDrawCommandPrimitiveId(PrimitiveSceneInfo, BatchElement, DrawPrimitiveId, ScenePrimitiveId);

            // 最后处理MeshDrawCommand
			FMeshProcessorShaders ShadersForDebugging = PassShaders.GetUntypedShaders();
			DrawListContext->FinalizeCommand(MeshBatch, BatchElementIndex, DrawPrimitiveId, ScenePrimitiveId, MeshFillMode, MeshCullMode, SortKey, PipelineState, &ShadersForDebugging, MeshDrawCommand);
		}
	}
}

经过以上的一些步骤,最终实现了FMeshBatch转换为FMeshDrawCommand!

那么总结一下FMeshPassProcessor大概的作用:

  1. Pass过滤。将该Pass无关的MeshBatch给过滤掉,比如深度Pass过滤掉透明物体。
  2. 选择绘制命令所需的Shader及渲染状态(深度、模板、混合状态、光栅化状态等)。
  3. 收集绘制命令涉及的Shader资源绑定。
    1. Pass的Uniform Buffer,如ViewUniformBuffer、DepthPassUniformBuffer。
    2. 顶点工厂绑定(顶点数据和索引)。
    3. 材质绑定。
    4. Pass的与绘制指令相关的绑定。
  4. 收集Draw Call相关的参数。

三、自定义MeshPass

若我们需要自定义一个MeshPass,需要做以下几件事情。笔者这里仅仅指出逻辑和要点。

如需具体的实现参考可以查看以下博客:

3.1 声明一个Pass

Shader端:

  • 新建着色器文件;

C++端:

  • 声明MeshPass枚举类型;
  • 继承FMeshPassProcessor,定义一个新的PassProcessor(重写AddMeshBatch的函数),并向引擎注册创建函数;
  • 创建新的Mesh着色器类(被新的PassProcessor);

3.2 收集BatchMesh

对于Dynamic(动态Mesh):

  • void FSceneRenderer::GatherDynamicMeshElements 函数中调用ComputeDynamicMeshRelevance进行收集!
  • 根据FPrimitiveViewRelevance的相关数据可以添加对应的Pass!
const int32 NumElements = MeshBatch.Mesh->Elements.Num();

if (ViewRelevance.bDrawRelevance && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth || ViewRelevance.bRenderInDepthPass))
{
    // 只要满足需要深度都会将EMeshPass::DepthPass对应的Mask置为真!
    PassMask.Set(EMeshPass::DepthPass);
    // 并添加元素个数!
    View.NumVisibleDynamicMeshElements[EMeshPass::DepthPass] += NumElements;

    if (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth)
    {
        // 同理,如需要Basepass!
        PassMask.Set(EMeshPass::BasePass);
        View.NumVisibleDynamicMeshElements[EMeshPass::BasePass] += NumElements;
    }

    // ... 省略部分源码
}

对于Cached(静态Mesh):

  • SceneVisibility.cpp中的MarkRelevant函数!

  • 在以下注释后面可以添加相应支持的MeshPass类型!

// Mark static mesh as visible for rendering
if (StaticMeshRelevance.bUseForMaterial && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth))
{
    DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::BasePass);
    MarkMask |= EMarkMaskBits::StaticMeshVisibilityMapMask;

    if (StaticMeshRelevance.bUseAnisotropy)
    {
        DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::AnisotropyPass);
    }

    if (ShadingPath == EShadingPath::Mobile)
    {
        DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::MobileBasePassCSM);
    }
    else if(StaticMeshRelevance.bUseSkyMaterial)
    {
        // Not needed on Mobile path as in this case everything goes into the regular base pass
        DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::SkyPass);
    }
    else if(StaticMeshRelevance.bUseSingleLayerWaterMaterial)
    {
        // Not needed on Mobile path as in this case everything goes into the regular base pass
        DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::SingleLayerWaterPass);
    }
    // ... 省略更多源码

3.3 修改渲染管线

再上一步,收集完FMeshBatch之后,自定义MeshProcess会通过Process函数(一般会模仿源码写成这个函数)进行处理,最终生成FMeshDrawCommand。

完成3.1和3.2两个步骤步骤之后,我们就获得了一系列的FMeshDrawCommand。

然后,我们需要在合适的时机,调用和触发这些FMeshDrawCommand,提交渲染命令。

例如,BasePass最终会进行调用,触发DrawCall!

GraphBuilder.AddPass(
    RDG_EVENT_NAME("BasePassParallel"),
    PassParameters,
    ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
    [this, &View, PassParameters](FRHICommandListImmediate& RHICmdList)
    {
        Scene->UniformBuffers.UpdateViewUniformBuffer(View);
        FRDGParallelCommandListSet ParallelCommandListSet(RHICmdList, GET_STATID(STAT_CLP_BasePass), *this, View, FParallelCommandListBindings(PassParameters));
        // DispatchDraw
        View.ParallelMeshDrawCommandPasses[EMeshPass::BasePass].DispatchDraw(&ParallelCommandListSet, RHICmdList);
    });

这些渲染调用的流程都需要通过硬编码来实现!!!

当然,自定义的MeshPass可能需要额外的RenderTarget并且将其应用到渲染流程中!

渲染目标的创建可以在FSceneRenderTargets类中找到参考。

四、BasePass

在延迟渲染管线下,BasePass的主要功能就是将需要渲染的Mesh的材质属性渲染到GBuffer中。

BasePass主要相关的代码如下:

C++层:

  • BasePassRendering.h
  • BasePassRendering.cpp
  • BasePassRendering.inl

Shader层:

  • BasePassVertexCommon.h
  • BasePassVertexShader.usf
  • BasePasssTessellationShaders.usf
  • BasePassCommon.ush
  • BasePassPixelShader.usf
  • PixelShaderOutputCommon.ush

4.1 BasePass逻辑

// TODO

4.2 BasePass着色器

// TODO

五、LightingPass

在延迟渲染管线下,LightingPass的主要功能就是进行光照着色!

利用GBuffer中的材质属性进行光照计算,从而实现场景着色。

LightingPass主要相关的代码如下:

C++层:

  • LightRendering.h
  • LightRendering.cpp

Shader层:

  • DeferredLightingCommmon.ush
  • DeferredLightPixelShaders.usf
  • DeferredLightVertexShaders.usf

5.1 LightingPass逻辑

// TODO

5.2 LightingPass着色器

// TODO

参考博文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值