前言
最近通过一些UE4的图形效果定制来学习UE4的图形渲染, 在自定义的MeshPass这个专题想通过移动端的边缘发光来阐述下定制流程. 在知乎大佬某篇文章 Unreal添加自定义Pass?恰好有相应的实现,但是我读过他的实现代码,发现部分代码耦合度有点高,过多把代码放到FMobileBasePassMeshProcessor上,所以我做出了部分改进.
边缘发光实现思路
边缘发光有很多实现办法, 目前这里采用MultiRenderPass的办法, 也就是对一个需要边缘发光的物体进行两次Pass
BasePass: 正常渲染一遍物体
OutLinePass: 让物体的顶点向法线方向进行外扩, 并对物体进行背面剔除
MeshPass
MeshPass就是UE4对StaticMesh, SkeletalMesh, ProceduralMesh等等进行的一次渲染Pass都是MeshPass.
FPrimitiveSceneProxy
MeshPass在游戏线程的中间对象是FPrimitiveSceneProxy,在FPrimitiveSceneProxy的GetDynamicMeshElements函数(渲染线程)中 MeshPass会被转为FMeshBatch也就是上图的黄色部分的场景渲染代理,具体可以参考(UE4 4.27)自定义PrimitiveComponent_带帯大师兄的博客-CSDN博客
?FMeshPassProcessor
?FMeshPassProcessor基本概念
MeshPass在转为FMeshBatch之后会进一步被相应的FMeshPassProcessor进行处理,最后在FMeshPassProcessor的AddMeshBatch函数中用BuildMeshDrawCommands把MeshBatch转为更底层的渲染指令.参考上图的红色部分。
这里的FMeshPassProcessor我理解为一个渲染阶段,比如RenderGbuffer,RenderShadow,RenderSSR都是渲染阶段,如果你想添加一个渲染Mesh的阶段,你就得定义新的FMeshPassProcessor, 比如FMobileBasePassMeshProcessor实现了Mobile端对物体的基础渲染。
?FMeshPassProcessor的处理的基本对象
?每个FMeshPassProcessor得绑定相应的FMeshMaterialShader, 负责处理相应阶段的Mesh, 格式和FGlobalShader类似。
所有不同类型的FMeshBatch(StaticMesh,LandscapeMesh, SkeletalMesh)最终在这个MeshPassProcessor代表的渲染阶段运行对应的FMeshMaterialShader。这个问题是:不同的Mesh的顶点工厂(顶点格式)是不一样的,比如StaticMesh和SkeletalMesh的VertexFactory差异性就很大,怎么让同一份FMeshMaterialShader代码运行不同顶点工厂的Mesh原理是自动模板编译,以MobileBasePassVertexShader.usf 为例子
?
?FVertexFactoryInput代表顶点的格式,同一个?FVertexFactoryInput绝对不可能兼容所有类型的Mesh格式,UE4做个奇妙的涉及,自动编译Shader, 根据Mesh的顶点工厂类型都来把相应的顶点工厂代码套入到相应MeshShader的模板代码,也就是#include "/Engine/Generated/VertexFactory.ush",当然除了顶点格式配套,还有某些同名的转换函数实现也得配套
比如局部顶点工厂:
?比如地形顶点工厂:
?
?FMeshPassProcessor的筛选
?如果你只想对某类Mesh进行处理转为渲染指令,一般是通过材质, DepthStencilState(是否渲染深度模板)等信息来筛选的,比如FSkyPassMeshProcessor就是只对天空材质的Mesh才进行渲染.边缘发光的筛选也是参考开头文章自定义了新的ShadingModel: BorderGlow
?
FMobileBorderGlowPassMeshProcessor
上面我说过OutlinePass耦合在MobileBasePass不太好,因为FMobileBasePassMeshProcessor以及对应的MeshShader代码以及很庞大了,通过把OutlinePass植入MobileBasePass显得很臃肿。所以为了实现边缘发光,我另外新开了一个FMeshPassProcessor
BorderGlowRendering.h
#pragma once
#include "CoreMinimal.h"
#include "MeshMaterialShader.h"
#include "MeshMaterialShaderType.h"
#include "MeshPassProcessor.h"
class FBorderGlowMeshVS : public FMeshMaterialShader
{
public:
DECLARE_SHADER_TYPE(FBorderGlowMeshVS, MeshMaterial);
FBorderGlowMeshVS() {};
FBorderGlowMeshVS(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{
BorderGlowSize.Bind(Initializer.ParameterMap, TEXT("BorderGlowSize"));
}
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)
{
FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);
}
private:
LAYOUT_FIELD(FShaderParameter, BorderGlowSize);
//LAYOUT_FIELD(FShaderParameter, BorderGlowColor);
};
class FBorderGlowMeshPS : public FMeshMaterialShader
{
public:
DECLARE_SHADER_TYPE(FBorderGlowMeshPS, MeshMaterial);
FBorderGlowMeshPS() {};
FBorderGlowMeshPS(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer)
: FMeshMaterialShader(Initializer)
{
BorderGlowColor.Bind(Initializer.ParameterMap, TEXT("BorderGlowColor"));
}
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)
{
FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);
}
private:
LAYOUT_FIELD(FShaderParameter, BorderGlowColor);
};
class FMobileBorderGlowPassMeshProcessor : public FMeshPassProcessor
{
public:
FMobileBorderGlowPassMeshProcessor(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);
};
BorderGlowRendering.cpp
#include "BorderGlowRendering.h"
#include "MaterialShaderType.h"
#include "ScenePrivate.h"
#include "VertexFactory.h"
#include "Shader.h"
#include "MeshPassProcessor.inl"
IMPLEMENT_MATERIAL_SHADER_TYPE(, FBorderGlowMeshVS, TEXT("/Engine/Private/BorderGlowVS.usf"), TEXT("Main"), SF_Vertex);
IMPLEMENT_MATERIAL_SHADER_TYPE(, FBorderGlowMeshPS, TEXT("/Engine/Private/BorderGlowPS.usf"), TEXT("Main"), SF_Pixel);
FMobileBorderGlowPassMeshProcessor::FMobileBorderGlowPassMeshProcessor(
const FScene* Scene,
const FSceneView* InViewIfDynamicMeshCommand,
const FMeshPassProcessorRenderState& InPassDrawRenderState,
FMeshPassDrawListContext* InDrawListContext)
: FMeshPassProcessor(Scene, Scene->GetFeatureLevel(), InViewIfDynamicMeshCommand, InDrawListContext)
, PassDrawRenderState(InPassDrawRenderState)
{
}
FMeshPassProcessor* CreateMobileGlowRenderPassProcessor(const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext)
{
FMeshPassProcessorRenderState PassDrawRenderState(Scene->UniformBuffers.ViewUniformBuffer, Scene->UniformBuffers.MobileOpaqueBasePassUniformBuffer);
PassDrawRenderState.SetInstancedViewUniformBuffer(Scene->UniformBuffers.InstancedViewUniformBuffer);
PassDrawRenderState.SetBlendState(TStaticBlendStateWriteMask<CW_RGBA>::GetRHI());
PassDrawRenderState.SetDepthStencilAccess(Scene->DefaultBasePassDepthStencilAccess);
PassDrawRenderState.SetDepthStencilState(TStaticDepthStencilState<true, CF_DepthNearOrEqual>::GetRHI());
return new(FMemStack::Get()) FMobileBorderGlowPassMeshProcessor(Scene, InViewIfDynamicMeshCommand, PassDrawRenderState, InDrawListContext);
}
bool GetBorderGlowPassShaders(
const FMaterial& Material,
FVertexFactoryType* VertexFactoryType,
TShaderRef<FBorderGlowMeshVS>& VertexShader,
TShaderRef<FBorderGlowMeshPS>& PixelShader)
{
FMaterialShaderTypes ShaderTypes;
FMaterialShaders Shaders;
ShaderTypes.AddShaderType<FBorderGlowMeshVS>();
ShaderTypes.AddShaderType<FBorderGlowMeshPS>();
if (!Material.TryGetShaders(ShaderTypes, VertexFactoryType, Shaders))
{
return false;
}
Shaders.TryGetVertexShader(VertexShader);
Shaders.TryGetPixelShader(PixelShader);
return true;
}
void FMobileBorderGlowPassMeshProcessor::Process(
const FMeshBatch& MeshBatch,
uint64 BatchElementMask,
const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
int32 StaticMeshId,
const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
const FMaterial& RESTRICT MaterialResource,
ERasterizerFillMode MeshFillMode,
ERasterizerCullMode MeshCullMode)
{
const FVertexFactory* VertexFactory = MeshBatch.VertexFactory;
TMeshProcessorShaders<
FBorderGlowMeshVS,
FBaseHS,
FBaseDS,
FBorderGlowMeshPS> BorderGlowPassShader;
if (!GetBorderGlowPassShaders(MaterialResource, VertexFactory->GetType(), BorderGlowPassShader.VertexShader, BorderGlowPassShader.PixelShader))
return;
FMeshMaterialShaderElementData ShaderElementData;
ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, StaticMeshId, false);
const FMeshDrawCommandSortKey SortKey = CalculateMeshStaticSortKey(BorderGlowPassShader.VertexShader, BorderGlowPassShader.PixelShader);
MeshCullMode = ERasterizerCullMode::CM_CCW;
BuildMeshDrawCommands(
MeshBatch,
BatchElementMask,
PrimitiveSceneProxy,
MaterialRenderProxy,
MaterialResource,
PassDrawRenderState,
BorderGlowPassShader,
MeshFillMode,
MeshCullMode,
SortKey,
EMeshPassFeatures::Default,
ShaderElementData);
}
void FMobileBorderGlowPassMeshProcessor::AddMeshBatch(const FMeshBatch& RESTRICT MeshBatch, uint64 BatchElementMask, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy, int32 StaticMeshId)
{
const FMaterialRenderProxy* FallbackMaterialRenderProxyPtr = nullptr;
const FMaterial& Material = MeshBatch.MaterialRenderProxy->GetMaterialWithFallback(FeatureLevel, FallbackMaterialRenderProxyPtr);
if (Material.GetShadingModels().HasShadingModel(MSM_BorderGlow))
{
const FMeshDrawingPolicyOverrideSettings OverrideSettings = ComputeMeshOverrideSettings(MeshBatch);
const ERasterizerFillMode MeshFillMode = ComputeMeshFillMode(MeshBatch, Material, OverrideSettings);
const ERasterizerCullMode MeshCullMode = ComputeMeshCullMode(MeshBatch, Material, OverrideSettings);
const FMaterialRenderProxy& MaterialRenderProxy = FallbackMaterialRenderProxyPtr ? *FallbackMaterialRenderProxyPtr : *MeshBatch.MaterialRenderProxy;
Process(MeshBatch, BatchElementMask, PrimitiveSceneProxy, StaticMeshId, MaterialRenderProxy, Material, MeshFillMode, MeshCullMode);
}
}
FRegisterPassProcessorCreateFunction RegisterMobileBorderGlowPass(&CreateMobileGlowRenderPassProcessor, EShadingPath::Mobile, EMeshPass::MobileBorderGlow, EMeshPassFlags::CachedMeshCommands | EMeshPassFlags::MainView);
BorderGlowVS.usf
#include "Common.ush"
#include "MobileBasePassCommon.ush"
#include "/Engine/Generated/Material.ush"
#include "/Engine/Generated/VertexFactory.ush"
struct FMobileShadingBasePassVSToPS
{
FVertexFactoryInterpolantsVSToPS FactoryInterpolants;
FMobileBasePassInterpolantsVSToPS BasePassInterpolants;
float4 Position : SV_POSITION;
};
void Main(
FVertexFactoryInput Input,
OPTIONAL_VertexID
out FMobileShadingBasePassVSToPS 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);
half3 WorldPositionOffset = GetMaterialWorldPositionOffset(VertexParameters);
WorldPosition.xyz += WorldPositionOffset;
float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPosition);
Output.Position = mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip);
}
BorderGlowPS.usf
#include "Common.ush"
#include "/Engine/Generated/Material.ush"
#include "/Engine/Generated/VertexFactory.ush"
#include "MobileBasePassCommon.ush"
void Main(
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
OutlineColor = GetOutlineColor0(MaterialParameters);
#endif
OutColor = half4(OutlineColor, 1.0);
}
VertexOffset和OutlineColor的处理
VertexOffset的外扩顶点方式我和?Unreal添加自定义Pass? 一样的,采用了WorldVertexNormal输入WorldOffset
?至于OutlineColor的话 很多默认参数已经被BasePass“霸占”,额外定义一个边缘发光色输入节点
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "MaterialExpressionIO.h"
#include "Materials/MaterialExpressionCustomOutput.h"
#include "MaterialExpressionOutlineColorOutput.generated.h"
UCLASS(collapsecategories, hidecategories=Object, MinimalAPI)
class UMaterialExpressionOutlineColorOutput : public UMaterialExpressionCustomOutput
{
GENERATED_UCLASS_BODY()
UPROPERTY(meta = (RequiredInput = "true"))
FExpressionInput Input;
#if WITH_EDITOR
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
virtual void GetCaption(TArray<FString>& OutCaptions) const override;
virtual uint32 GetInputType(int32 InputIndex) override { return MCT_Float3; }
virtual FExpressionInput* GetInput(int32 InputIndex) override;
#endif
virtual int32 GetNumOutputs() const override { return 1; }
virtual FString GetFunctionName() const override { return TEXT("GetOutlineColor"); }
virtual FString GetDisplayName() const override { return TEXT("OutlineColor"); }
};
UMaterialExpressionOutlineColorOutput::UMaterialExpressionOutlineColorOutput(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
#if WITH_EDITORONLY_DATA
// Structure to hold one-time initialization
struct FConstructorStatics
{
FText NAME_Utility;
FConstructorStatics(const FString& DisplayName, const FString& FunctionName)
: NAME_Utility(LOCTEXT("Utility", "Utility"))
{
}
};
static FConstructorStatics ConstructorStatics(GetDisplayName(), GetFunctionName());
MenuCategories.Add(ConstructorStatics.NAME_Utility);
bCollapsed = true;
// No outputs
Outputs.Reset();
#endif
}
#if WITH_EDITOR
int32 UMaterialExpressionOutlineColorOutput::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex)
{
if (Input.GetTracedInput().Expression)
{
return Compiler->CustomOutput(this, OutputIndex, Input.Compile(Compiler));
}
else
{
return CompilerError(Compiler, TEXT("Input missing"));
}
return INDEX_NONE;
}
void UMaterialExpressionOutlineColorOutput::GetCaption(TArray<FString>& OutCaptions) const
{
OutCaptions.Add(FString(TEXT("OutlineColor")));
}
FExpressionInput* UMaterialExpressionOutlineColorOutput::GetInput(int32 InputIndex)
{
return &Input;
}
#endif // WITH_EDITOR
参考GetBentNormal材质节点的实现
实现效果
git引擎代码实现链接
[1]??????????https://github.com/2047241149/UnrealEngine-1/commit/63e281326621d25ca66393f8623f81427faa1878
?
参考资料
[1]?Unreal添加自定义Pass
[2]Unreal自定义ShaderModel
[3]MeshDrawingPipeline
[4]https://programmersought.com/article/36239010339/
|