UE里默认不支持多Pass,项目里一般都会需要这个功能。

记录一下一种实现思路。

效果视频:

StaticMesh多Pass

实现主要是两个方面,一个是材质管理,另一个是把多Pass加入渲染流程中。

材质管理

首先需要在UStaticMesh和UStaticMeshComponent里新增多Pass材质。

UStaticMesh是用资产编辑器打开的窗口,最里层的材质设置。

UStaticMeshComponent是SM要渲染的必要组件,可以设置材质进行覆盖UStaticMesh里的材质。(常见里摆放或者蓝图里)

多Pass的结构定义,材质Index以及是否投阴影。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//~ StaticMesh的多pass材质结构,选择和FStaticMaterial对应,就不用去对应LOD的各种琐碎问题
USTRUCT(BlueprintType)
struct FStaticMultiMaterial
{
GENERATED_USTRUCT_BODY()

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = StaticMesh)
TObjectPtr<class UMaterialInterface> MaterialInterface;

//插槽名字没用
/*This name should be use by the gameplay to avoid error if the skeletal mesh Materials array topology change*/
//UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = StaticMesh)
//FName MaterialSlotName;

//材质Index,对应原本的材质Index,最好是数量和index一一对应,就可以做到与LOD无关
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = StaticMesh)
int MaterialIndex;

//是否CastShadow
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = StaticMesh)
uint8 bCastShadow : 1;

FStaticMultiMaterial()
: MaterialInterface(NULL)
{

}

};

在UStaticMesh和UStaticMeshComponent里定义材质列表。

1
2
3
4
----------UStaticMesh里-------------
//StaticMesh的多pass材质
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = StaticMesh)
TArray<FStaticMultiMaterial> StaticMultiPassMaterials;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--------StaticMeshComponent里----------
//Loy MultiPass MultiPass开关
UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category = MultiPassMat)
uint8 bEnableMultiPassMaterial : 1;

//Loy 是否覆盖StaticMesh原本的多Pass材质
UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category = MultiPassMat)
uint8 bOverrideStaticMeshMultiPassMaterial : 1;

//Loy Override的材质列表
UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category = MultiPassMat)
TArray<struct FStaticMultiMaterial> OverrideStaticMultiMaterials;

-----一些Get方法----------
//loy GetSMMultiPassMats
const TArray<struct FStaticMultiMaterial> GetStaticMultiPassMaterials() const;
virtual FStaticMultiMaterial GetMultiPassMaterial(int32 MaterialIndex) const;
virtual UMaterialInterface* GetEditorMultiPassMaterial(int32 MaterialIndex) const;

其中GetMaterial的几个方法里,基本就是按照MaterialIndex来传递材质,首先获取UStaticMesh的,如果StaticMeshComponent Override了,就返回SMComponent里的材质。

加入MultiPass

负责StaticMesh渲染的大多在StaticMeshRender里,FStaticMeshSceneProxy在渲染线程里的操作。

需要把多Pass加入到中DrawStaticElements和GetDynamicMeshElements两个方法里。

DrawStaticElements方法通常在SMSceneProxy在加入到场景里的时候调用,或者被移动了造成SceneProxy重新创建的时候调用。

GetDynamicMeshElements方法在动态绘制路径。每帧收集MeshBatch的路径,默认SM有这个方法,就加了进去。。

SkeletalMesh多Pass

蒙皮网格的多pass实现思路和静态的差不多,就是渲染路径只有动态的了。

也是材质管理和插入多pass到收集MeshBatch的地方。

材质管理

首先在SkeletalMesh和SkinnedMeshComponent里新增材质列表。

SkeletalMesh就是资源编辑器打开资产的窗口,美术可以预设多pass材质。

SkinnedMeshComponent(继承下去的SkeletalMeshComponent)是场景里渲染SkeletalMesh用,可以设置并覆盖资产的材质列表。

SK多Pass的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//SK的多pass材质结构
USTRUCT(BlueprintType)
struct FSkeletalMultiPassMaterial
{
GENERATED_USTRUCT_BODY()

//对应主材质的Index,这里不关联Section,因为LOD里Section里有材质index的对应关系
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SkeletalMesh)
int32 MaterialIndex;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SkeletalMesh)
TObjectPtr<class UMaterialInterface> MaterialInterface;

};

SkeletalMesh里新增材质列表。

1
2
3
//** List of MultiPassMaterials apply to this mesh -Loy */
UPROPERTY(EditAnywhere, BlueprintReadOnly, duplicatetransient, Category = MultiPassMaterial)
TArray<FSkeletalMultiPassMaterial> MultiPassMaterials;

SkinnedMeshComponent里新增材质列表和覆盖开关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	//是否开启多pass
UPROPERTY(EditAnywhere, Category = "Materials")
uint8 bEnableMultiPassMaterial : 1;

//是否override SK里的多pass材质
UPROPERTY(EditAnywhere, Category = "Materials")
uint8 bOverrideSKMultiPassMaterial : 1;

UPROPERTY(EditAnywhere, Category = "Materials")
TArray<FSkeletalMultiPassMaterial> MultiPassMaterials;

------一些Get方法--------
//Loy MultiPass
UMaterialInterface* GetMultiPassMaterial(int32 MaterialIndex) const;
TArray<FSkeletalMultiPassMaterial> GetMultiPassMaterials() const;
UMaterialInterface* GetOverrideMultiPassMaterial(int32 MaterialIndex) const;

加入多Pass

因为SKMesh是动态绘制路径,所以就在GetDynamicMeshElements里加入多Pass的MeshBatch。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FSkeletalMeshSceneProxy::GetDynamicMeshElements
——————>FSkeletalMeshSceneProxy::GetMeshElementsConditionallySelectable
{
GetDynamicElementsSection(Views, ViewFamily, VisibilityMap, LODData, LODIndex, SectionIndex, bSectionSelected, SectionElementInfo, bInSelectable, Collector);

//加入多Pass MeshBatch
if(bEnableMultiPassMaterial)
{
FSectionElementInfo MultiPassSectionInfo(nullptr, SectionElementInfo.bEnableShadowCasting, SectionElementInfo.UseMaterialIndex);
MultiPassSectionInfo.Material = SectionElementInfo.MultiPassMaterial;

if(MultiPassSectionInfo.Material)
GetDynamicElementsSection(Views, ViewFamily, VisibilityMap, LODData, LODIndex, SectionIndex, bSectionSelected, MultiPassSectionInfo, false, Collector);
}

}