14 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	
			14 KiB
		
	
	
	
	
	
	
	
title, date, excerpt, tags, rating
| title | date | excerpt | tags | rating | 
|---|---|---|---|---|
| 基于插件的StaticMesh多Pass绘制方案 | 2022-11-04 10:26:59 | ⭐ | 
前言
StaticMesh与SkeletalMesh的实现方法比较类似,不过两者的绘制方式有很大的不同。不过庆幸的是,Static的MeshBatch设置在绘制函数的外面。
思路说明
创建自定义StaticMeshComponent
- 继承UStaticMeshComponent,重写GetUsedMaterials与CreateSceneProxy函数。
 - 添加用于多pass渲染的UMaterialInterface指针。(NeedSecondPass变量其实可以不用,因为既然你要使用这个类,那肯定要启用这个功能)
 
#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;
};
- 实现GetUsedMaterials与CreateSceneProxy函数,并包含相应头文件。
 
头文件
#include "StrokeStaticMeshSceneProxy.h"
GetUsedMaterials
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
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。
- 继承FStaticMeshSceneProxy,重写DrawStaticElements函数。
 - 定义UStaticMeshComponent指针,用于获取自定义StaticMeshComponent的变量。(之后使用需要使用强制转换)
 
#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;
};
- 复制DrawStaticElements中所需函数的代码。 将Engine\Source\Runtime\Engine\Private\StaticMeshRender.cpp中的AllowShadowOnlyMesh、UseLightPropagationVolumeRT2函数代码复制到你定义的StaticMeshSceneProxy类所在的cpp文件中。
 - 实现DrawStaticElements函数,并包含头文件。
 
与SkeletalMesh的绘制函数不同,它有3处需要调用绘制函数的地方。其中有一处因为不存在lod所以代码有些不同,所以请不要在未测试的情况下使用本插件进行生产项目。
不过一个好消息是,我们可以在绘制前设置MeshBatch,从而完美结局描边问题。
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
使用方法
- 创建一个Actor蓝图。
 - 创建你定义的StaticMeshComponent。
 - 赋予轮廓材质,并勾选NeedSecondPass。
 
使用这个材质的效果: