说明
本例中,使用添加自定义MeshPass的方式,在添加Shading Model的基础上,对Toon Shading Model增加描边效果。
TODO
根据UE Mesh Drawing Pipeline中对MeshPass的总结,可以知道新增自定义MeshPass需要在Pipeline的进行如下方面的修改:
- 新增MeshPass声明
- 创建自定义MeshPass需要使用的MeshMaterialShader
- FMeshMaterialShader子类声明及实现
- Shader代码
- 创建自定义MeshPass对应的MeshPassProcessor
- 重载AddMeshBatch函数
- 实现Process函数(可选,仅为调用的封装,全都写在AddMeshBatch里也可以,内置MeshPassProcessor使用两种做法的都有)
- 向引擎注册创建函数
- 设置PassMask,将MeshBatch加入自定义MeshPass(Cached & Dynamic)
- 修改FSceneRenderer
- 加入自定义MeshPass对应的MeshDrawCommand的收集
- 在合适的位置使用RDG设置绘制
总结来说,实际上需要做的就是准备好生成MeshDrawCommand所需的各种资源,并在FSceneRenderer中加入对自定义MeshPass的Dispatch,并在该Dispatch逻辑中创建对应的MeshPassProcessor,在其重载的AddMeshBatch函数(Process函数非必需,只是某些MeshPassProcessor多加一层调用的封装,并非从FMeshPassProcessor继承而来)中完成资源的收集,并送给基类FMeshPassProcessor的BuildMeshDrawCommands函数完成MeshDrawCommand的创建收集。
具体步骤
新增MeshPass声明
// Engine\Source\Runtime\Renderer\Public\MeshPassProcessor.h
namespace EMeshPass
{
enum Type : uint8
{
DepthPass,
SecondStageDepthPass,
(...)
WaterInfoTextureDepthPass,
WaterInfoTexturePass,
/********** MyDrawBorderMeshPass *****/
MyDrawBorderMeshPass,
/*************************************/
#if WITH_EDITOR
HitProxy,
HitProxyOpaqueOnly,
EditorLevelInstance,
EditorSelection,
#endif
Num,
NumBits = 6,
};
}
inline const TCHAR* GetMeshPassName(EMeshPass::Type MeshPass)
{
switch (MeshPass)
{
case EMeshPass::DepthPass: return TEXT("DepthPass");
(...)
case EMeshPass::WaterInfoTextureDepthPass: return TEXT("WaterInfoTextureDepthPass");
case EMeshPass::WaterInfoTexturePass: return TEXT("WaterInfoTexturePass");
/********** MyDrawBorderMeshPass *****/
case EMeshPass::MyDrawBorderMeshPass: return TEXT("MyDrawBorderMeshPass");
/*************************************/
#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");
#endif
}
/********** MyDrawBorderMeshPass *****/
#if WITH_EDITOR
static_assert(EMeshPass::Num == 33 + 4, "Need to update switch(MeshPass) after changing EMeshPass"); // GUID to prevent incorrect auto-resolves, please change when changing the expression: {674D7D62-CFD8-4971-9A8D-CD91E5612CD8}
#else
static_assert(EMeshPass::Num == 33, "Need to update switch(MeshPass) after changing EMeshPass"); // GUID to prevent incorrect auto-resolves, please change when changing the expression: {674D7D62-CFD8-4971-9A8D-CD91E5612CD8}
#endif
/*************************************/
checkf(0, TEXT("Missing case for EMeshPass %u"), (uint32)MeshPass);
return nullptr;
}
创建自定义MeshPass需要使用的MeshMaterialShader
FMeshMaterialShader子类声明及实现
声明
// Engine\Source\Runtime\Renderer\Public\MyDrawBorderMeshPassRendering.h
// ilyaxu
#pragma once
#include "CoreMinimal.h"
#include "MeshMaterialShader.h"
#include "MeshMaterialShaderType.h"
#include "MeshPassProcessor.h"
class FMyDrawBorderMeshPassVS : public FMeshMaterialShader
{
public:
DECLARE_SHADER_TYPE(FMyDrawBorderMeshPassVS, MeshMaterial);
FMyDrawBorderMeshPassVS() {};
FMyDrawBorderMeshPassVS(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{
BorderThickness.Bind(Initializer.ParameterMap, TEXT("BorderThickness"));
}
static bool ShouldCompilePermutation(const FShaderPermutationParameters& Parameters)
{
return true;
}
// static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
// {
// FMeshMaterialShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
// }
void GetShaderBindings(const FScene* Scene,
ERHIFeatureLevel::Type FeatureLevel,
const FPrimitiveSceneProxy* PrimitiveSceneProxy,
const FMaterialRenderProxy& MaterialRenderProxy,
const FMaterial& Material,
const FMeshPassProcessorRenderState& DrawRenderState,
const FMeshMaterialShaderElementData& ShaderElementData,
FMeshDrawSingleShaderBindings& ShaderBindings) const
{
FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);
}
private:
LAYOUT_FIELD(FShaderParameter, BorderThickness);
};
class FMyDrawBorderMeshPassPS : public FMeshMaterialShader
{
public:
DECLARE_SHADER_TYPE(FMyDrawBorderMeshPassPS, MeshMaterial);
FMyDrawBorderMeshPassPS() {};
FMyDrawBorderMeshPassPS(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{
BorderColor.Bind(Initializer.ParameterMap, TEXT("BorderColor"));
}
static bool ShouldCompilePermutation(const FShaderPermutationParameters& Parameters)
{
return true;
}
// static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
// {
// FMeshMaterialShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
// }
void GetShaderBindings(const FScene* Scene,
ERHIFeatureLevel::Type FeatureLevel,
const FPrimitiveSceneProxy* PrimitiveSceneProxy,
const FMaterialRenderProxy& MaterialRenderProxy,
const FMaterial& Material,
const FMeshPassProcessorRenderState& DrawRenderState,
const FMeshMaterialShaderElementData& ShaderElementData,
FMeshDrawSingleShaderBindings& ShaderBindings) const
{
FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);
}
private:
LAYOUT_FIELD(FShaderParameter, BorderColor);
};
实现
// Engine\Source\Runtime\Renderer\Private\MyDrawBorderMeshPassRendering.cpp
IMPLEMENT_MATERIAL_SHADER_TYPE(, FMyDrawBorderMeshPassVS, TEXT("/Engine/Private/FMyDrawBorderMeshPassShader.usf"), TEXT("MainVS"), SF_Vertex);
IMPLEMENT_MATERIAL_SHADER_TYPE(, FMyDrawBorderMeshPassPS, TEXT("/Engine/Private/FMyDrawBorderMeshPassShader.usf"), TEXT("MainPS"), SF_Pixel);
HLSL代码
// Engine\Shaders\Private\FMyDrawBorderMeshPassShader.usf
// ilyaxu
#include "Common.ush"
#include "MobileBasePassCommon.ush"
#include "/Engine/Generated/Material.ush"
#include "/Engine/Generated/VertexFactory.ush"
struct FMyDrawBorderMeshPassShaderVStoPS
{
FVertexFactoryInterpolantsVSToPS FactoryInterpolants;
FMobileBasePassInterpolantsVSToPS BasePassInterpolants;
float4 Position : SV_POSITION;
};
void MainVS(
FVertexFactoryInput Input,
out FMyDrawBorderMeshPassShaderVStoPS Output)
{
ResolvedView = ResolveView();
FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
float4 WorldPositionExcludingWPO = VertexFactoryGetWorldPosition(Input, VFIntermediates);
float4 WorldPosition = WorldPositionExcludingWPO;
half3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);
FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPosition.xyz, TangentToLocal);
#if MATERIAL_SHADINGMODEL_TOON
half3 WorldPositionOffset = GetMaterialWorldPositionOffset(VertexParameters);
#else
half3 WorldPositionOffset = 0;
#endif
WorldPosition.xyz += WorldPositionOffset;
float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPosition);
Output.Position = mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip);
}
void MainPS(
FVertexFactoryInterpolantsVSToPS Interpolants
, FMobileBasePassInterpolantsVSToPS BasePassInterpolants
, in float4 SvPosition : SV_Position
, out half4 OutColor : SV_Target0)
{
const bool bIsFrontFace = false;
FMaterialPixelParameters MaterialParameters = GetMaterialPixelParameters(Interpolants, SvPosition);
FPixelMaterialInputs PixelMaterialInputs;
{
float4 ScreenPosition = SvPositionToResolvedScreenPosition(SvPosition);
float3 WorldPosition = BasePassInterpolants.PixelPosition.xyz;
float3 WorldPositionExcludingWPO = BasePassInterpolants.PixelPosition.xyz;
#if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
WorldPositionExcludingWPO = BasePassInterpolants.PixelPositionExcludingWPO;
#endif
CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, ScreenPosition, bIsFrontFace, WorldPosition, WorldPositionExcludingWPO);
#if FORCE_VERTEX_NORMAL
// Quality level override of material's normal calculation, can be used to avoid normal map reads etc.
MaterialParameters.WorldNormal = MaterialParameters.TangentToWorld[2];
MaterialParameters.ReflectionVector = ReflectionAboutCustomWorldNormal(MaterialParameters, MaterialParameters.WorldNormal, false);
#endif
}
#if !EARLY_Z_PASS_ONLY_MATERIAL_MASKING
//Clip if the blend mode requires it.
GetMaterialCoverageAndClipping(MaterialParameters, PixelMaterialInputs);
#endif
half3 OutlineColor = half3(0.0, 0.0, 0.0);
// #if NUM_MATERIAL_OUTPUTS_GETOUTLINECOLOR > 0 && MATERIAL_SHADINGMODEL_TOON
// OutlineColor = GetBorderColor0(MaterialParameters);
// #endif
#if MATERIAL_SHADINGMODEL_TOON
OutlineColor = half3(1.0, 1.0, 1.0);
#endif
OutColor = half4(OutlineColor, 1.0);
}
// Engine\Shaders\Private\MobileBasePassVertexShader.usf
void Main(
FVertexFactoryInput Input
, out FMobileShadingBasePassVSOutput Output
#if INSTANCED_STEREO
, out uint LayerIndex : SV_RenderTargetArrayIndex
#elif MOBILE_MULTI_VIEW
, in uint ViewId : SV_ViewID
#endif
)
{
(...)
// float3 WorldPositionOffset = GetMaterialWorldPositionOffset(VertexParameters);
#if MATERIAL_SHADINGMODEL_TOON
float3 WorldPositionOffset = float3(0.0, 0.0, 0.0);
#else
float3 WorldPositionOffset = GetMaterialWorldPositionOffset(VertexParameters);
#endif
(...)
}
创建MeshPassProcessor
MeshPassProcessor声明
// Engine\Source\Runtime\Renderer\Public\MyDrawBorderMeshPassRendering.h
// ilyaxu
#pragma once
#include "CoreMinimal.h"
#include "MeshMaterialShader.h"
#include "MeshMaterialShaderType.h"
#include "MeshPassProcessor.h"
class FMyDrawBorderMeshPassProcessor : public FMeshPassProcessor
{
public:
// UE5.4建议使用如下形式的Ctor,增加一个参数EMeshPass::Type
FMyDrawBorderMeshPassProcessor(
EMeshPass::Type InMeshPassType,
const FScene* Scene,
const FSceneView* InViewIfDynamicMeshCommand,
const FMeshPassProcessorRenderState& InPassDrawRenderState,
FMeshPassDrawListContext* InDrawListContext);
virtual void AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId = -1) override final;
public:
FMeshPassProcessorRenderState PassDrawRenderState;
private:
void Process(
const FMeshBatch& MeshBatch,
uint64 BatchElementMask,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
int32 StaticMeshId,
const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
const FMaterial& RESTRICT MaterialResource,
ERasterizerFillMode MeshFillMode,
ERasterizerCullMode MeshCullMode);
};
// Engine\Source\Runtime\Renderer\Private\MyDrawBorderMeshPassRendering.cpp
FMyDrawBorderMeshPassProcessor::FMyDrawBorderMeshPassProcessor(
EMeshPass::Type InMeshPassType,
const FScene* Scene,
const FSceneView* InViewIfDynamicMeshCommand,
const FMeshPassProcessorRenderState& InPassDrawRenderState,
FMeshPassDrawListContext* InDrawListContext)
: FMeshPassProcessor(InMeshPassType, Scene, Scene->GetFeatureLevel(), InViewIfDynamicMeshCommand, InDrawListContext)
, PassDrawRenderState(InPassDrawRenderState)
{
}
重载AddMeshBatch函数
在AddMeshBatch函数中对当前Material所用的ShadingModel进行判断,仅对自定义的Toon Shading Model进行描边
// Engine\Source\Runtime\Renderer\Private\MyDrawBorderMeshPassRendering.cpp
void FMyDrawBorderMeshPassProcessor::AddMeshBatch(const FMeshBatch& MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* PrimitiveSceneProxy, int32 StaticMeshId)
{
const FMaterialRenderProxy* FallbackMaterialRenderProxyPtr = nullptr;
const FMaterial& Material = MeshBatch.MaterialRenderProxy->GetMaterialWithFallback(FeatureLevel, FallbackMaterialRenderProxyPtr);
if (Material.GetShadingModels().HasShadingModel(MSM_Toon))
{
const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch);
const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(Material, OverrideSettings);
const ERasterizerCullMode MeshCullMode = ComputeMeshCullMode(Material, OverrideSettings);
const FMaterialRenderProxy& MaterialRenderProxy = FallbackMaterialRenderProxyPtr ? *FallbackMaterialRenderProxyPtr : *MeshBatch.MaterialRenderProxy;
Process(MeshBatch, BatchElementMask, PrimitiveSceneProxy, StaticMeshId, MaterialRenderProxy, Material, MeshFillMode, MeshCullMode);
}
}
实现Process函数(可选)
// Engine\Source\Runtime\Renderer\Private\MyDrawBorderMeshPassRendering.cpp
void FMyDrawBorderMeshPassProcessor::Process(const FMeshBatch& MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* PrimitiveSceneProxy, int32 StaticMeshId, const FMaterialRenderProxy& MaterialRenderProxy, const FMaterial& MaterialResource, ERasterizerFillMode MeshFillMode, ERasterizerCullMode MeshCullMode)
{
const FVertexFactory* VertexFactory = MeshBatch.VertexFactory;
TMeshProcessorShaders<
FMyDrawBorderMeshPassVS,
FMyDrawBorderMeshPassPS> BorderGlowPassShaders;
if (!GetMyDrawBorderMeshPassShaders(MaterialResource, VertexFactory->GetType(), BorderGlowPassShaders.VertexShader, BorderGlowPassShaders.PixelShader))
return;
FMeshMaterialShaderElementData ShaderElementData;
ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, StaticMeshId, false);
const FMeshDrawCommandSortKey SortKey = CalculateMeshStaticSortKey(BorderGlowPassShaders.VertexShader, BorderGlowPassShaders.PixelShader);
MeshCullMode = ERasterizerCullMode::CM_CCW;
BuildMeshDrawCommands(
MeshBatch,
BatchElementMask,
PrimitiveSceneProxy,
MaterialRenderProxy,
MaterialResource,
PassDrawRenderState,
BorderGlowPassShaders,
MeshFillMode,
MeshCullMode,
SortKey,
EMeshPassFeatures::Default,
ShaderElementData);
}
向引擎注册创建函数
定义MeshProcessor创建函数,并向引擎注册,以便后续可通过FPassProcessorManager::CreateMeshPassProcessor来创建
// Engine\Source\Runtime\Renderer\Public\MyDrawBorderMeshPassRendering.h
FMeshPassProcessor* CreateMyDrawBorderMeshPassProcessor(ERHIFeatureLevel::Type FeatureLevel, const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext)
{
FMeshPassProcessorRenderState PassDrawRenderState;
PassDrawRenderState.SetBlendState(TStaticBlendStateWriteMask<CW_RGBA>::GetRHI());
PassDrawRenderState.SetDepthStencilAccess(Scene->DefaultBasePassDepthStencilAccess);
PassDrawRenderState.SetDepthStencilState(TStaticDepthStencilState<true, CF_DepthNearOrEqual>::GetRHI());
return new FMyDrawBorderMeshPassProcessor(EMeshPass::MyDrawBorderMeshPass, Scene, InViewIfDynamicMeshCommand, PassDrawRenderState, InDrawListContext);
}
// UE5 使用下列宏来将MeshPassProcessor注册进FPassProcessorManager和FPSOCollectorCreateManager
REGISTER_MESHPASSPROCESSOR_AND_PSOCOLLECTOR(MyDrawBorderMeshPass, CreateMyDrawBorderMeshPassProcessor, EShadingPath::Mobile, EMeshPass::MyDrawBorderMeshPass, EMeshPassFlags::CachedMeshCommands | EMeshPassFlags::MainView);
设置PassMask
Dynamic
// Engine\Source\Runtime\Renderer\Private\SceneVisibility.cpp
static void ComputeDynamicMeshRelevance(
EShadingPath ShadingPath,
bool bAddLightmapDensityCommands,
const FPrimitiveViewRelevance& ViewRelevance,
const FMeshBatchAndRelevance& MeshBatch,
FViewInfo& View,
FMeshPassMask& PassMask,
FPrimitiveSceneInfo* PrimitiveSceneInfo,
const FPrimitiveBounds& Bounds)
{
const int32 NumElements = MeshBatch.Mesh->Elements.Num();
if (ViewRelevance.bDrawRelevance && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth || ViewRelevance.bRenderInDepthPass))
{
(...)
if (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth)
{
(...)
if (ShadingPath == EShadingPath::Mobile)
{
PassMask.Set(EMeshPass::MobileBasePassCSM);
View.NumVisibleDynamicMeshElements[EMeshPass::MobileBasePassCSM] += NumElements;
PassMask.Set(EMeshPass::MyDrawBorderMeshPass);
View.NumVisibleDynamicMeshElements[EMeshPass::MyDrawBorderMeshPass] += NumElements;
}
(...)
}
}
(...)
}
Static
// Engine\Source\Runtime\Renderer\Private\SceneVisibility.cpp
void FRelevancePacket::ComputeRelevance(FDynamicPrimitiveIndexList& DynamicPrimitiveIndexList)
{
(...)
if ((StaticMeshRelevance.bUseForMaterial || StaticMeshRelevance.bUseAsOccluder)
&& (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth || ViewRelevance.bRenderInDepthPass)
&& !bHiddenByHLODFade)
{
(...)
// Mark static mesh as visible for rendering
if (StaticMeshRelevance.bUseForMaterial && (ViewRelevance.bRenderInMainPass || ViewRelevance.bRenderCustomDepth))
{
// Specific logic for mobile packets
if (ShadingPath == EShadingPath::Mobile)
{
// Skydome must not be added to base pass bucket
if (!StaticMeshRelevance.bUseSkyMaterial)
{
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::BasePass);
if (!bMobileBasePassAlwaysUsesCSM)
{
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::MobileBasePassCSM);
DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, CullingPayloadFlags, Scene, bCanCache, EMeshPass::MyDrawBorderMeshPass);
}
}
(...)
}
(...)
}
(...)
}
(...)
}
修改FSceneRenderer
描边的MeshPass需要加载BasePass之后,需要进行的主要有两项:收集MeshDrawCommand,以及在BasePass之后进行绘制。
收集MeshDrawCommand
在BasePass入队生成MeshDrawCommand任务之后添加
// Engine\Source\Runtime\Renderer\Private\MobileShadingRenderer.cpp
void FMobileSceneRenderer::SetupMobileBasePassAfterShadowInit(FExclusiveDepthStencil::Type BasePassDepthStencilAccess, TArrayView<FViewCommands> ViewCommandsPerView, FInstanceCullingManager& InstanceCullingManager)
{
for (int32 ViewIndex = 0; ViewIndex < AllViews.Num(); ++ViewIndex)
{
(...)
FName PassName(GetMeshPassName(EMeshPass::BasePass));
Pass.DispatchPassSetup(
Scene,
View,
FInstanceCullingContext(PassName, ShaderPlatform, &InstanceCullingManager, ViewIds, nullptr, InstanceCullingMode),
EMeshPass::BasePass,
BasePassDepthStencilAccess,
MeshPassProcessor,
View.DynamicMeshElements,
&View.DynamicMeshElementsPassRelevance,
View.NumVisibleDynamicMeshElements[EMeshPass::BasePass],
ViewCommands.DynamicMeshCommandBuildRequests[EMeshPass::BasePass],
ViewCommands.DynamicMeshCommandBuildFlags[EMeshPass::BasePass],
ViewCommands.NumDynamicMeshCommandBuildRequestElements[EMeshPass::BasePass],
ViewCommands.MeshCommands[EMeshPass::BasePass],
BasePassCSMMeshPassProcessor,
&ViewCommands.MeshCommands[EMeshPass::MobileBasePassCSM]);
/********** MyDrawBorderMeshPass *****/
FMeshPassProcessor* MyDrawBorderMeshPassProcessor = FPassProcessorManager::CreateMeshPassProcessor(EShadingPath::Mobile, EMeshPass::MyDrawBorderMeshPass, Scene->GetFeatureLevel(), Scene, &View, nullptr);
FParallelMeshDrawCommandPass& MyDrawBorderPass = View.ParallelMeshDrawCommandPasses[EMeshPass::MyDrawBorderMeshPass];
FName MyDrawBorderPassName(GetMeshPassName(EMeshPass::MyDrawBorderMeshPass));
MyDrawBorderPass.DispatchPassSetup(
Scene,
View,
FInstanceCullingContext(MyDrawBorderPassName, ShaderPlatform, &InstanceCullingManager, ViewIds, nullptr, InstanceCullingMode),
EMeshPass::MyDrawBorderMeshPass,
BasePassDepthStencilAccess,
MeshPassProcessor,
View.DynamicMeshElements,
&View.DynamicMeshElementsPassRelevance,
View.NumVisibleDynamicMeshElements[EMeshPass::MyDrawBorderMeshPass],
ViewCommands.DynamicMeshCommandBuildRequests[EMeshPass::MyDrawBorderMeshPass],
ViewCommands.DynamicMeshCommandBuildFlags[EMeshPass::MyDrawBorderMeshPass],
ViewCommands.NumDynamicMeshCommandBuildRequestElements[EMeshPass::MyDrawBorderMeshPass],
ViewCommands.MeshCommands[EMeshPass::MyDrawBorderMeshPass]);
/*************************************/
}
}
// Engine\Source\Runtime\Renderer\Private\SceneRendering.cpp
void FSceneRenderer::SetupMeshPass(FViewInfo& View, FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FViewCommands& ViewCommands, FInstanceCullingManager& InstanceCullingManager)
{
SCOPE_CYCLE_COUNTER(STAT_SetupMeshPass);
const EShadingPath ShadingPath = GetFeatureLevelShadingPath(Scene->GetFeatureLevel());
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 || PassType == EMeshPass::MyDrawBorderMeshPass))
{
continue;
}
(...)
}
}
}
在BasePass后设置绘制
// Engine\Source\Runtime\Renderer\Private\MobileShadingRenderer.cpp
void FMobileSceneRenderer::RenderMyDrawBorderMeshPass(FRHICommandListImmediate& RHICmdList, const TArrayView<const FViewInfo*> PassViews)
{
for (int32 ViewIndex = 0; ViewIndex < PassViews.Num(); ViewIndex++)
{
SCOPED_CONDITIONAL_DRAW_EVENTF(RHICmdList, EventView, Views.Num() > 1, TEXT("View%d"), ViewIndex);
const FViewInfo& View = *PassViews[ViewIndex];
if (!View.ShouldRenderView())
{
continue;
}
RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1);
View.ParallelMeshDrawCommandPasses[EMeshPass::MyDrawBorderMeshPass].DispatchDraw(nullptr, RHICmdList);
}
}
void FMobileSceneRenderer::RenderForwardSinglePass(FRDGBuilder& GraphBuilder, FMobileRenderPassParameters* PassParameters, FRenderViewContext& ViewContext, FSceneTextures& SceneTextures)
{
(...)
GraphBuilder.AddPass(
RDG_EVENT_NAME("SceneColorRendering"),
PassParameters,
// the second view pass should not be merged with the first view pass on mobile since the subpass would not work properly.
ERDGPassFlags::Raster | ERDGPassFlags::NeverMerge,
[this, PassParameters, ViewContext, bDoOcclusionQueries, &SceneTextures](FRHICommandList& RHICmdList)
{
(...)
// Depth pre-pass
RHICmdList.SetCurrentStat(GET_STATID(STAT_CLM_MobilePrePass));
RenderMaskedPrePass(RHICmdList, View);
// Opaque and masked
RHICmdList.SetCurrentStat(GET_STATID(STAT_CLMM_Opaque));
RenderMobileBasePass(RHICmdList, View, &PassParameters->InstanceCullingDrawParams);
/********** MyDrawBorderMeshPass *****/
RenderMyDrawBorderMeshPass(RHICmdList, View);
/*************************************/
(...)
});
(...)
}
效果
ToonShadingModel描边效果

RenderDoc截帧
使用宏添加统计数据以使自定义MeshPass能被RenderDoc捕获
// Engine\Source\Runtime\RenderCore\Public\RenderCore.h
(...)
DECLARE_CYCLE_STAT_EXTERN(TEXT("Depth drawing"),STAT_DepthDrawTime,STATGROUP_SceneRendering, RENDERCORE_API);
DECLARE_CYCLE_STAT_EXTERN(TEXT("Base pass drawing"),STAT_BasePassDrawTime,STATGROUP_SceneRendering, RENDERCORE_API);
/********************/
DECLARE_CYCLE_STAT_EXTERN(TEXT("My draw border pass drawing"),STAT_MyDrawBorderPassDrawTime,STATGROUP_SceneRendering, RENDERCORE_API);
/********************/
DECLARE_CYCLE_STAT_EXTERN(TEXT("Anisotropy pass drawing"), STAT_AnisotropyPassDrawTime, STATGROUP_SceneRendering, RENDERCORE_API);
DECLARE_CYCLE_STAT_EXTERN(TEXT("Water pass drawing"), STAT_WaterPassDrawTime, STATGROUP_SceneRendering, RENDERCORE_API);
(...)
// Engine\Source\Runtime\RenderCore\Private\RenderCore.cpp
(...)
DEFINE_STAT(STAT_StaticDrawListDrawTime);
DEFINE_STAT(STAT_BasePassDrawTime);
/********************/
DEFINE_STAT(STAT_MyDrawBorderPassDrawTime);
/********************/
DEFINE_STAT(STAT_AnisotropyPassDrawTime);
DEFINE_STAT(STAT_DepthDrawTime);
(...)
// Engine\Source\Runtime\Renderer\Private\MobileBasePassRendering.cpp
void FMobileSceneRenderer::RenderMyDrawBorderMeshPass(FRHICommandList& RHICmdList, const FViewInfo& View)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(RenderMyDrawBorderMeshPass);
SCOPED_DRAW_EVENT(RHICmdList, MyDrawBorderMeshPass);
SCOPE_CYCLE_COUNTER(STAT_MyDrawBorderPassDrawTime);
RHICmdList.SetViewport(View.ViewRect.Min.X, View.ViewRect.Min.Y, 0, View.ViewRect.Max.X, View.ViewRect.Max.Y, 1);
View.ParallelMeshDrawCommandPasses[EMeshPass::MyDrawBorderMeshPass].DispatchDraw(nullptr, RHICmdList);
}

Reference
Unreal Engine Documentation: Render Dependency Graph