373 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			373 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
---
 | 
						||
title: 基于插件的StaticMesh多Pass绘制方案
 | 
						||
date: 2022-11-04 10:26:59
 | 
						||
excerpt: 
 | 
						||
tags: 
 | 
						||
rating: ⭐
 | 
						||
---
 | 
						||
## 前言
 | 
						||
StaticMesh与SkeletalMesh的实现方法比较类似,不过两者的绘制方式有很大的不同。不过庆幸的是,Static的MeshBatch设置在绘制函数的外面。
 | 
						||
## 思路说明
 | 
						||
### 创建自定义StaticMeshComponent
 | 
						||
1. 继承UStaticMeshComponent,重写GetUsedMaterials与CreateSceneProxy函数。
 | 
						||
2. 添加用于多pass渲染的UMaterialInterface指针。(NeedSecondPass变量其实可以不用,因为既然你要使用这个类,那肯定要启用这个功能)
 | 
						||
```c++
 | 
						||
#pragma once
 | 
						||
#include "CoreMinimal.h"
 | 
						||
#include "Engine/Classes/Components/StaticMeshComponent.h"
 | 
						||
#include "StrokeStaticMeshComponent.generated.h"
 | 
						||
 | 
						||
UCLASS(ClassGroup=(Rendering, Common), hidecategories=Object,  editinlinenew, meta=(BlueprintSpawnableComponent))
 | 
						||
class UStrokeStaticMeshComponent : public UStaticMeshComponent
 | 
						||
{
 | 
						||
	GENERATED_UCLASS_BODY()
 | 
						||
 | 
						||
	//~ Begin UPrimitiveComponent Interface
 | 
						||
	virtual void GetUsedMaterials(TArray<UMaterialInterface*>& OutMaterials, bool bGetDebugMaterials = false) const override;
 | 
						||
 | 
						||
	virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
 | 
						||
	//~ End UPrimitiveComponent Interface
 | 
						||
public:
 | 
						||
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiplePass")
 | 
						||
	UMaterialInterface* SecondPassMaterial = nullptr;
 | 
						||
 | 
						||
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiplePass")
 | 
						||
	bool NeedSecondPass=false;
 | 
						||
};
 | 
						||
```
 | 
						||
3. 实现GetUsedMaterials与CreateSceneProxy函数,并包含相应头文件。
 | 
						||
### 头文件
 | 
						||
```
 | 
						||
#include "StrokeStaticMeshSceneProxy.h"
 | 
						||
```
 | 
						||
### GetUsedMaterials
 | 
						||
```c++
 | 
						||
void UStrokeStaticMeshComponent::GetUsedMaterials(TArray<UMaterialInterface*>& OutMaterials, bool bGetDebugMaterials /*= false*/) const
 | 
						||
{
 | 
						||
	if (GetStaticMesh() && GetStaticMesh()->RenderData)
 | 
						||
	{
 | 
						||
		TMap<int32, UMaterialInterface*> MapOfMaterials;
 | 
						||
		for (int32 LODIndex = 0; LODIndex < GetStaticMesh()->RenderData->LODResources.Num(); LODIndex++)
 | 
						||
		{
 | 
						||
			FStaticMeshLODResources& LODResources = GetStaticMesh()->RenderData->LODResources[LODIndex];
 | 
						||
			int32 MaterialNum = 0;
 | 
						||
			for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++)
 | 
						||
			{
 | 
						||
				// Get the material for each element at the current lod index
 | 
						||
				int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex;
 | 
						||
				if (!MapOfMaterials.Contains(MaterialIndex))
 | 
						||
				{
 | 
						||
					MapOfMaterials.Add(MaterialIndex, GetMaterial(MaterialIndex));
 | 
						||
					MaterialNum++;
 | 
						||
				}
 | 
						||
			}
 | 
						||
            //这里是我添加的代码
 | 
						||
			if (NeedSecondPass)
 | 
						||
			{
 | 
						||
				bool NeedAddMaterial = true;
 | 
						||
				for (int i = 0; i < MapOfMaterials.Num(); ++i)
 | 
						||
				{
 | 
						||
					if (MapOfMaterials[i]== SecondPassMaterial)
 | 
						||
					{
 | 
						||
						NeedAddMaterial = false;
 | 
						||
					}
 | 
						||
				}
 | 
						||
				if (NeedAddMaterial)
 | 
						||
				{
 | 
						||
					MapOfMaterials.Add(MaterialNum, SecondPassMaterial);
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
		if (MapOfMaterials.Num() > 0)
 | 
						||
		{
 | 
						||
			//We need to output the material in the correct order (follow the material index)
 | 
						||
			//So we sort the map with the material index
 | 
						||
			MapOfMaterials.KeySort([](int32 A, int32 B) {
 | 
						||
				return A < B; // sort keys in order
 | 
						||
			});
 | 
						||
 | 
						||
			//Preadd all the material item in the array
 | 
						||
			OutMaterials.AddZeroed(MapOfMaterials.Num());
 | 
						||
			//Set the value in the correct order
 | 
						||
			int32 MaterialIndex = 0;
 | 
						||
			for (auto Kvp : MapOfMaterials)
 | 
						||
			{
 | 
						||
				OutMaterials[MaterialIndex++] = Kvp.Value;
 | 
						||
			}
 | 
						||
		}
 | 
						||
	}
 | 
						||
}
 | 
						||
```
 | 
						||
### CreateSceneProxy
 | 
						||
```c++
 | 
						||
FPrimitiveSceneProxy* UStrokeStaticMeshComponent::CreateSceneProxy()
 | 
						||
{
 | 
						||
	if (GetStaticMesh() == nullptr || GetStaticMesh()->RenderData == nullptr)
 | 
						||
	{
 | 
						||
		return nullptr;
 | 
						||
	}
 | 
						||
 | 
						||
	const TIndirectArray<FStaticMeshLODResources>& LODResources = GetStaticMesh()->RenderData->LODResources;
 | 
						||
	if (LODResources.Num() == 0 || LODResources[FMath::Clamp<int32>(GetStaticMesh()->MinLOD.Default, 0, LODResources.Num() - 1)].VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0)
 | 
						||
	{
 | 
						||
		return nullptr;
 | 
						||
	}
 | 
						||
    //与SkeletalMeshComponent中写的类似,直接通过new来创建
 | 
						||
	FPrimitiveSceneProxy* Proxy = ::new FStrokeStaticMeshSceneProxy(this, false);
 | 
						||
#if STATICMESH_ENABLE_DEBUG_RENDERING
 | 
						||
	SendRenderDebugPhysics(Proxy);
 | 
						||
#endif
 | 
						||
	return Proxy;
 | 
						||
}
 | 
						||
```
 | 
						||
### 创建自定义StaticMeshSceneProxy
 | 
						||
**这里与SkeletalMeshSceneProxy不同,GetDynamicMeshElements没有起到绘制作用,真正起到作用的是DrawStaticElements。**
 | 
						||
1. 继承FStaticMeshSceneProxy,重写DrawStaticElements函数。
 | 
						||
2. 定义UStaticMeshComponent指针,用于获取自定义StaticMeshComponent的变量。(之后使用需要使用强制转换)
 | 
						||
```c++
 | 
						||
#pragma once
 | 
						||
#include "Engine\Public\StaticMeshResources.h"
 | 
						||
 | 
						||
class FStrokeStaticMeshSceneProxy : public FStaticMeshSceneProxy
 | 
						||
{
 | 
						||
public:
 | 
						||
	//在UStrokeStaticMeshComponent中调用这个构造函数初始化
 | 
						||
	FStrokeStaticMeshSceneProxy(UStaticMeshComponent* Component, bool bForceLODsShareStaticLighting);
 | 
						||
 | 
						||
	virtual void DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) override;
 | 
						||
	
 | 
						||
private:
 | 
						||
	UPROPERTY()
 | 
						||
	const UStaticMeshComponent* ComponentPtr;
 | 
						||
};
 | 
						||
```
 | 
						||
3. 复制DrawStaticElements中所需函数的代码。
 | 
						||
将Engine\Source\Runtime\Engine\Private\StaticMeshRender.cpp中的AllowShadowOnlyMesh、UseLightPropagationVolumeRT2函数代码复制到你定义的StaticMeshSceneProxy类所在的cpp文件中。
 | 
						||
4. 实现DrawStaticElements函数,并包含头文件。
 | 
						||
 | 
						||
与SkeletalMesh的绘制函数不同,它有3处需要调用绘制函数的地方。其中有一处因为不存在lod所以代码有些不同,所以请不要在未测试的情况下使用本插件进行生产项目。
 | 
						||
 | 
						||
不过一个好消息是,我们可以在绘制前设置MeshBatch,从而完美结局描边问题。
 | 
						||
```c++
 | 
						||
void FStrokeStaticMeshSceneProxy::DrawStaticElements(FStaticPrimitiveDrawInterface* PDI)
 | 
						||
{
 | 
						||
	checkSlow(IsInParallelRenderingThread());
 | 
						||
	if (!HasViewDependentDPG())
 | 
						||
	{
 | 
						||
		// Determine the DPG the primitive should be drawn in.
 | 
						||
		uint8 PrimitiveDPG = GetStaticDepthPriorityGroup();
 | 
						||
		int32 NumLODs = RenderData->LODResources.Num();
 | 
						||
		//Never use the dynamic path in this path, because only unselected elements will use DrawStaticElements
 | 
						||
		bool bIsMeshElementSelected = false;
 | 
						||
		const auto FeatureLevel = GetScene().GetFeatureLevel();
 | 
						||
 | 
						||
		//check if a LOD is being forced
 | 
						||
		if (ForcedLodModel > 0)
 | 
						||
		{
 | 
						||
			int32 LODIndex = FMath::Clamp(ForcedLodModel, ClampedMinLOD + 1, NumLODs) - 1;
 | 
						||
			const FStaticMeshLODResources& LODModel = RenderData->LODResources[LODIndex];
 | 
						||
			// Draw the static mesh elements.
 | 
						||
			for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
 | 
						||
			{
 | 
						||
#if WITH_EDITOR
 | 
						||
				if (GIsEditor)
 | 
						||
				{
 | 
						||
					const FLODInfo::FSectionInfo& Section = LODs[LODIndex].Sections[SectionIndex];
 | 
						||
 | 
						||
					bIsMeshElementSelected = Section.bSelected;
 | 
						||
					PDI->SetHitProxy(Section.HitProxy);
 | 
						||
				}
 | 
						||
#endif // WITH_EDITOR
 | 
						||
 | 
						||
				const int32 NumBatches = GetNumMeshBatches();
 | 
						||
 | 
						||
				PDI->ReserveMemoryForMeshes(NumBatches);
 | 
						||
 | 
						||
				for (int32 BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++)
 | 
						||
				{
 | 
						||
					FMeshBatch MeshBatch;
 | 
						||
 | 
						||
					if (GetMeshElement(LODIndex, BatchIndex, SectionIndex, PrimitiveDPG, bIsMeshElementSelected, true, MeshBatch))
 | 
						||
					{
 | 
						||
						PDI->DrawMesh(MeshBatch, FLT_MAX);
 | 
						||
                        //以下是我添加的代码
 | 
						||
						const FLODInfo& ProxyLODInfo = LODs[LODIndex];
 | 
						||
						UMaterialInterface* MaterialInterface = ProxyLODInfo.Sections[SectionIndex].Material;
 | 
						||
 | 
						||
						const UStrokeStaticMeshComponent* StrokeStaticMeshComponent = dynamic_cast<const UStrokeStaticMeshComponent *>(ComponentPtr);
 | 
						||
						if (MaterialInterface == StrokeStaticMeshComponent->SecondPassMaterial)
 | 
						||
						{
 | 
						||
							continue;
 | 
						||
						}
 | 
						||
						if (StrokeStaticMeshComponent->NeedSecondPass) {
 | 
						||
							if (StrokeStaticMeshComponent->SecondPassMaterial == nullptr) {
 | 
						||
								continue;
 | 
						||
							}
 | 
						||
							MeshBatch.MaterialRenderProxy = StrokeStaticMeshComponent->SecondPassMaterial->GetRenderProxy();
 | 
						||
							//设置反转剔除选项
 | 
						||
							MeshBatch.ReverseCulling = true;
 | 
						||
							PDI->DrawMesh(MeshBatch, FLT_MAX);
 | 
						||
						}
 | 
						||
					}
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
		else //no LOD is being forced, submit them all with appropriate cull distances
 | 
						||
		{
 | 
						||
			for (int32 LODIndex = ClampedMinLOD; LODIndex < NumLODs; LODIndex++)
 | 
						||
			{
 | 
						||
				const FStaticMeshLODResources& LODModel = RenderData->LODResources[LODIndex];
 | 
						||
				float ScreenSize = GetScreenSize(LODIndex);
 | 
						||
 | 
						||
				bool bUseUnifiedMeshForShadow = false;
 | 
						||
				bool bUseUnifiedMeshForDepth = false;
 | 
						||
 | 
						||
				if (GUseShadowIndexBuffer && LODModel.bHasDepthOnlyIndices)
 | 
						||
				{
 | 
						||
					const FLODInfo& ProxyLODInfo = LODs[LODIndex];
 | 
						||
 | 
						||
					// The shadow-only mesh can be used only if all elements cast shadows and use opaque materials with no vertex modification.
 | 
						||
					// In some cases (e.g. LPV) we don't want the optimization
 | 
						||
					bool bSafeToUseUnifiedMesh = AllowShadowOnlyMesh(FeatureLevel);
 | 
						||
 | 
						||
					bool bAnySectionUsesDitheredLODTransition = false;
 | 
						||
					bool bAllSectionsUseDitheredLODTransition = true;
 | 
						||
					bool bIsMovable = IsMovable();
 | 
						||
					bool bAllSectionsCastShadow = bCastShadow;
 | 
						||
 | 
						||
					for (int32 SectionIndex = 0; bSafeToUseUnifiedMesh && SectionIndex < LODModel.Sections.Num(); SectionIndex++)
 | 
						||
					{
 | 
						||
						const FMaterial* Material = ProxyLODInfo.Sections[SectionIndex].Material->GetRenderProxy()->GetMaterial(FeatureLevel);
 | 
						||
						// no support for stateless dithered LOD transitions for movable meshes
 | 
						||
						bAnySectionUsesDitheredLODTransition = bAnySectionUsesDitheredLODTransition || (!bIsMovable && Material->IsDitheredLODTransition());
 | 
						||
						bAllSectionsUseDitheredLODTransition = bAllSectionsUseDitheredLODTransition && (!bIsMovable && Material->IsDitheredLODTransition());
 | 
						||
						const FStaticMeshSection& Section = LODModel.Sections[SectionIndex];
 | 
						||
 | 
						||
						bSafeToUseUnifiedMesh =
 | 
						||
							!(bAnySectionUsesDitheredLODTransition && !bAllSectionsUseDitheredLODTransition) // can't use a single section if they are not homogeneous
 | 
						||
							&& Material->WritesEveryPixel()
 | 
						||
							&& !Material->IsTwoSided()
 | 
						||
							&& !IsTranslucentBlendMode(Material->GetBlendMode())
 | 
						||
							&& !Material->MaterialModifiesMeshPosition_RenderThread()
 | 
						||
							&& Material->GetMaterialDomain() == MD_Surface;
 | 
						||
 | 
						||
						bAllSectionsCastShadow &= Section.bCastShadow;
 | 
						||
					}
 | 
						||
 | 
						||
					if (bSafeToUseUnifiedMesh)
 | 
						||
					{
 | 
						||
						bUseUnifiedMeshForShadow = bAllSectionsCastShadow;
 | 
						||
 | 
						||
						// Depth pass is only used for deferred renderer. The other conditions are meant to match the logic in FDepthPassMeshProcessor::AddMeshBatch.
 | 
						||
						bUseUnifiedMeshForDepth = ShouldUseAsOccluder() && GetScene().GetShadingPath() == EShadingPath::Deferred && !IsMovable();
 | 
						||
 | 
						||
						if (bUseUnifiedMeshForShadow || bUseUnifiedMeshForDepth)
 | 
						||
						{
 | 
						||
							const int32 NumBatches = GetNumMeshBatches();
 | 
						||
 | 
						||
							PDI->ReserveMemoryForMeshes(NumBatches);
 | 
						||
 | 
						||
							for (int32 BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++)
 | 
						||
							{
 | 
						||
								FMeshBatch MeshBatch;
 | 
						||
 | 
						||
								if (GetShadowMeshElement(LODIndex, BatchIndex, PrimitiveDPG, MeshBatch, bAllSectionsUseDitheredLODTransition))
 | 
						||
								{
 | 
						||
									bUseUnifiedMeshForShadow = bAllSectionsCastShadow;
 | 
						||
 | 
						||
									MeshBatch.CastShadow = bUseUnifiedMeshForShadow;
 | 
						||
									MeshBatch.bUseForDepthPass = bUseUnifiedMeshForDepth;
 | 
						||
									MeshBatch.bUseAsOccluder = bUseUnifiedMeshForDepth;
 | 
						||
									MeshBatch.bUseForMaterial = false;
 | 
						||
 | 
						||
									PDI->DrawMesh(MeshBatch, ScreenSize);
 | 
						||
                                    //以下是我添加的代码
 | 
						||
									const FLODInfo& ProxyLODInfo = LODs[LODIndex];
 | 
						||
									UMaterialInterface* MaterialInterface = ProxyLODInfo.Sections[0].Material;
 | 
						||
 | 
						||
									const UStrokeStaticMeshComponent* StrokeStaticMeshComponent = dynamic_cast<const UStrokeStaticMeshComponent *>(ComponentPtr);
 | 
						||
									if (MaterialInterface == StrokeStaticMeshComponent->SecondPassMaterial)
 | 
						||
									{
 | 
						||
										continue;
 | 
						||
									}
 | 
						||
									if (StrokeStaticMeshComponent->NeedSecondPass) {
 | 
						||
										if (StrokeStaticMeshComponent->SecondPassMaterial == nullptr) {
 | 
						||
											continue;
 | 
						||
										}
 | 
						||
										//通过MeshBatch设置Material与反转剔除选项
 | 
						||
										MeshBatch.MaterialRenderProxy = StrokeStaticMeshComponent->SecondPassMaterial->GetRenderProxy();
 | 
						||
									    //设置反转剔除选项
 | 
						||
										MeshBatch.ReverseCulling = true;
 | 
						||
										PDI->DrawMesh(MeshBatch, FLT_MAX);
 | 
						||
									}
 | 
						||
								}
 | 
						||
							}
 | 
						||
						}
 | 
						||
					}
 | 
						||
				}
 | 
						||
 | 
						||
				// Draw the static mesh elements.
 | 
						||
				for (int32 SectionIndex = 0; SectionIndex < LODModel.Sections.Num(); SectionIndex++)
 | 
						||
				{
 | 
						||
#if WITH_EDITOR
 | 
						||
					if (GIsEditor)
 | 
						||
					{
 | 
						||
						const FLODInfo::FSectionInfo& Section = LODs[LODIndex].Sections[SectionIndex];
 | 
						||
 | 
						||
						bIsMeshElementSelected = Section.bSelected;
 | 
						||
						PDI->SetHitProxy(Section.HitProxy);
 | 
						||
					}
 | 
						||
#endif // WITH_EDITOR
 | 
						||
 | 
						||
					const int32 NumBatches = GetNumMeshBatches();
 | 
						||
 | 
						||
					PDI->ReserveMemoryForMeshes(NumBatches);
 | 
						||
 | 
						||
					for (int32 BatchIndex = 0; BatchIndex < NumBatches; BatchIndex++)
 | 
						||
					{
 | 
						||
						FMeshBatch MeshBatch;
 | 
						||
 | 
						||
						if (GetMeshElement(LODIndex, BatchIndex, SectionIndex, PrimitiveDPG, bIsMeshElementSelected, true, MeshBatch))
 | 
						||
						{
 | 
						||
							// If we have submitted an optimized shadow-only mesh, remaining mesh elements must not cast shadows.
 | 
						||
							MeshBatch.CastShadow &= !bUseUnifiedMeshForShadow;
 | 
						||
							MeshBatch.bUseAsOccluder &= !bUseUnifiedMeshForDepth;
 | 
						||
							MeshBatch.bUseForDepthPass &= !bUseUnifiedMeshForDepth;
 | 
						||
 | 
						||
							PDI->DrawMesh(MeshBatch, ScreenSize);
 | 
						||
                            //以下是我添加的代码
 | 
						||
							const FLODInfo& ProxyLODInfo = LODs[LODIndex];
 | 
						||
							UMaterialInterface* MaterialInterface = ProxyLODInfo.Sections[SectionIndex].Material;
 | 
						||
 | 
						||
							const UStrokeStaticMeshComponent* StrokeStaticMeshComponent = dynamic_cast<const UStrokeStaticMeshComponent *>(ComponentPtr);
 | 
						||
							if (MaterialInterface == StrokeStaticMeshComponent->SecondPassMaterial)
 | 
						||
							{
 | 
						||
								continue;
 | 
						||
							}
 | 
						||
							if (StrokeStaticMeshComponent->NeedSecondPass) {
 | 
						||
								if (StrokeStaticMeshComponent->SecondPassMaterial == nullptr) {
 | 
						||
									continue;
 | 
						||
								}
 | 
						||
								MeshBatch.MaterialRenderProxy = StrokeStaticMeshComponent->SecondPassMaterial->GetRenderProxy();
 | 
						||
								//
 | 
						||
								MeshBatch.ReverseCulling = true;
 | 
						||
								PDI->DrawMesh(MeshBatch, FLT_MAX);
 | 
						||
							}
 | 
						||
						}
 | 
						||
					}
 | 
						||
				}
 | 
						||
			}
 | 
						||
		}
 | 
						||
	}
 | 
						||
}
 | 
						||
```
 | 
						||
## 插件地址及使用方法
 | 
						||
### 插件地址
 | 
						||
https://github.com/blueroseslol/BRPlugins
 | 
						||
 | 
						||
### 使用方法
 | 
						||
1. 创建一个Actor蓝图。
 | 
						||
2. 创建你定义的StaticMeshComponent。
 | 
						||
3. 赋予轮廓材质,并勾选NeedSecondPass。
 | 
						||
 | 
						||
使用这个材质的效果: |