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。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								使用这个材质的效果:
							 |