当前位置:网站首页>Mesh merging under ue4/ue5 runtime

Mesh merging under ue4/ue5 runtime

2022-07-07 15:43:00 WhiteTian

Original article , Reprint please indicate the source .

Preface

Engine version :4.27.2
The premise of the merger : stay UE4.26.2 after , Runtime components are allowed UStaticmesh. The previous method only supports importing under the editor ,
After importing, it will be converted to UStaticMesh Of RenderData Rendering ;

Why merge :

The source of demand is software. We want to use UE4.27.2 Of runtime Under the udatasmith Import function ,
But because datasmith The original intention of our design is to split the model as small as possible , The particle size is very small .
So there are udatasmith After importing, there will be tens of thousands of... In the level , Very low frame rate .
That's why there is this article Merge at run time StaticMesh.

The optimization scheme of the combined batch is as follows

In fact, there are many that can be approved together .
therefore , Here I will focus on udatasmith Import this function , Studied the scheme of joint approval ;

programme advantage shortcoming
1> modify datasmith Import the code of some plug-ins Highest efficiency Difficult to maintain
2> Make your own set The efficiency is lower than the scheme 1, Easy maintenance Easy maintenance
3> modify datasmith Export plug-ins Not sure Difficult to maintain

Which scheme to use ?

programme 1 In a word , Change DataSmith Source code , I think efficiency is the best .
Why? :
Let's start with the plan 1 How to do it : One by one actor Not yet spawn,mesh Not yet build,collsion, There is no such information as material build Before , Let's filter in advance Mesh Can be merged , After that spawn MeshActor,build StaticMesh Of vertex,collision,material.
Let's talk about the plan 2 How to do it : be-all actor Already in the world spawn Come out ,StaticMesh Of vertex,collision,material This information has been build Okay . Then filter what is good Mesh Can be merged , After that spawn MeshActor,build StaticMesh Of vertex,collision,material.

The scheme is implemented temporarily 2

In contrast , programme 1 It's better than the plan 2 Efficient . But the plan 1 It's more troublesome to change , And I don't think it's easy to maintain . After watching it all day , I realized the scheme first 2.

Video effects :Merge Then the frame rate and DC Significantly improve Jump Watch

UE4/UE5 Runtime Lower merger Mesh

Class diagram

 Insert picture description here

Editor With the implementation of the

Reference resources MergeActor Tool
 Insert picture description here

Use the... Under the editor MergeActorTool The function of the tool , The merging logic will soon be implemented in the editor .Standlone You can also merge .
But it should be noted that this can only be used under the editor , Take a break when you pack .
The specific code of merging under the editor is as follows , As a reference :

// Merge method under editor 
void UMyBlueprintFunctionLibrary::MergeMy(const TArray<UPrimitiveComponent*>& ComponentsToMerge, UWorld* World,
	const FMeshMergingSettings& InSettings, UMaterialInterface* InBaseMaterial,
	UPackage* InOuter, const FString& InBasePackageName,
	TArray<UObject*>& OutAssetsToSync, FVector& OutMergedActorLocation,
	const float ScreenSize, bool bSilent /*= false*/, FString AppendName)
{
    
	const IMeshMergeUtilities& MeshUtilities = FModuleManager::Get().
	LoadModuleChecked<IMeshMergeModule>("MeshMergeUtilities").GetUtilities();

	MeshUtilities.MergeComponentsToStaticMeshWithName(ComponentsToMerge, GWorld, InSettings, InBaseMaterial, InOuter, InBasePackageName,
		OutAssetsToSync, OutMergedActorLocation, ScreenSize, bSilent, AppendName);
}

// Merge specific logic , Put the same material Mesh Pass it in to complete the merger .

TArray<UObject*> OutAssetsToSync;
FVector OutMergedActorLocation;
const float ScreenAreaSize = TNumericLimits<float>::Max();

FMeshMergingSettings setting;
setting.bMergePhysicsData = 1;
MergeMy(mergedata.Value, GWorld,
	setting, nullptr, GetTransientPackage(), FString(),
	OutAssetsToSync, OutMergedActorLocation,
	ScreenAreaSize, true, mergedata.Key);

UStaticMesh* UtilitiesMergedMesh = nullptr;
if (!OutAssetsToSync.FindItemByClass(&UtilitiesMergedMesh))
{
    
	// Error, TEXT("MergeStaticMeshActors failed. No mesh was created.
	continue;
}

for (auto obj : OutAssetsToSync)
{
    
	auto umesh = Cast<UStaticMesh>(obj);
	if (!umesh)
		continue;
	/*auto mat0 = umesh->GetMaterial(0); if (!UKismetSystemLibrary::IsValid(mat0)) continue;*/
	OutMergedActorLocation+=FVector(0,0,500);
	auto MergedActor = GWorld->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), OutMergedActorLocation, FRotator(0, 0, 0));
	if (MergedActor)
	{
    
		MergedActor->SetMobility(EComponentMobility::Movable);
		if (!MergedActor->GetStaticMeshComponent())
			continue;
		MergedActor->GetStaticMeshComponent()->SetStaticMesh(umesh);
		if (mergedata.Value.Num() > 0)
		{
    
			UStaticMeshComponent* pSTM = Cast<UStaticMeshComponent>(mergedata.Value[0]);
			if (pSTM)
			{
    
				//umesh->SetStaticMaterials(pSTM->GetStaticMesh()->GetStaticMaterials());
			}
		}
		GWorld->UpdateCullDistanceVolumes(MergedActor, MergedActor->GetStaticMeshComponent());
		MergedActor->AttachToActor(RootActor, FAttachmentTransformRules::KeepWorldTransform);
#if WITH_EDITOR
		MergedActor->SetActorLabel(UKismetSystemLibrary::GetDisplayName(umesh));
#endif // endif
	}
	
// Delete the replaced RootActor
for (auto willremovecomp : mergedata.Value)
{
    
	if(!IsValid(DeleteActorArray[willremovecomp]))
		continue;
	if(!DeleteActorArray[willremovecomp]->IsValidLowLevel())
		continue;
	TArray<UActorComponent*> OutComponent;
	OutComponent = DeleteActorArray[willremovecomp]->K2_GetComponentsByClass(UStaticMeshComponent::StaticClass());
	if (OutComponent.Num() < 2)
	{
    
		GWorld->DestroyActor(DeleteActorArray[willremovecomp]);
	}
	else
	{
    
		willremovecomp->DestroyComponent();
	}
}
}

Runtime With the implementation of the

difficulty 1,StaticMesh Of RenderData turn FMeshDescription

In fact, if you've seen this StaticMesh You should know , The code merged under the editor is used under the editor StaticMesh Unique data to merge , This is the picture below . The variables used are SourceModels
 Insert picture description here
also , Under the editor StaticMesh The build will eventually call Build Method , But these are not available at runtime .
We need to use... In the new version of the engine BuildFromStaticMeshDescriptions To generate UStaticMesh.
 Insert picture description here
BuildFromStaticMeshDescriptions What this method needs is FMeshDescription,FMeshDescription After importing in the editor, there will be , But at runtime UStaticMesh Of SourceModels Does not exist. , What do I do ?
We need to push back , The final rendered data exists UStaticMesh Of RenderData in , So we start from RenderData It turns the data into FMeshDescription Just array .

In turn, merge each one that can be merged Mesh The data from RenderData convert to FMeshDescription, Then put these
FMeshDescription Add once , Give it to UStaticMesh Of BuildFromStaticMeshDescriptions Pass it in and it's done ( Note the size of the data here ,UE Serialization of cannot exceed 2G, But fortunately, we wrote this by ourselves , And then splicing FMeshDescription Let's just control the memory when we need it , This is also related to the speed of merger )

To sum up, the specific steps are :
1>RenderData turn FMeshDescription
2> Splice all FMeshDescription
3> call BuildFromStaticMeshDescriptions

difficulty 2,StaticMesh Build complex collisions

To build complex collisions , So call
UBodySetup->CreatePhysicsMeshes(), If you follow carefully , When you go in, you'll find , stay Runtime Next ,build Collision will call ProcessFormatData_PhysX perhaps ProcessFormatData_Chaos, But the preconditions must be met IsRuntime The judgment of the .
The reason I found this is , After the merger , I'm creating UStaticMesh Objects are written in a common way ,NewObect(xxxxxxx), It turns out that IsRuntime My judgment there has always been false.

		if (IsRuntime(this))
		{
    
#if WITH_PHYSX && PHYSICS_INTERFACE_PHYSX
			bClearMeshes = !RuntimeCookPhysics_PhysX();
#elif WITH_CHAOS
			bClearMeshes = !RuntimeCookPhysics_Chaos();
#endif
		}
void UBodySetup::CreatePhysicsMeshes()
{
    
	TRACE_CPUPROFILER_EVENT_SCOPE(UBodySetup::CreatePhysicsMeshes);

	SCOPE_CYCLE_COUNTER(STAT_CreatePhysicsMeshes);

	// Create meshes from cooked data if not already done
	if(bCreatedPhysicsMeshes)
	{
    
		return;
	}

	// If we don't have any convex/trimesh data we can skip this whole function
	if (bNeverNeedsCookedCollisionData)
	{
    
		return;
	}
	
	bool bClearMeshes = true;

	// Find or create cooked physics data
	static FName PhysicsFormatName(FPlatformProperties::GetPhysicsFormat());

	FByteBulkData* FormatData = GetCookedData(PhysicsFormatName);

	// On dedicated servers we may be cooking generic data and sharing it
	if (FormatData == nullptr && IsRunningDedicatedServer())
	{
    
		FormatData = GetCookedData(FGenericPlatformProperties::GetPhysicsFormat());
	}

	if (FormatData)
	{
    
#if WITH_PHYSX && PHYSICS_INTERFACE_PHYSX
		bClearMeshes = !ProcessFormatData_PhysX(FormatData);
#elif WITH_CHAOS
		bClearMeshes = !ProcessFormatData_Chaos(FormatData);
#endif
	}
	else
	{
    
		if (IsRuntime(this))// This place is in Runtime Next, if you use UStaticMesh Words , It's impossible to pass .
		{
    
#if WITH_PHYSX && PHYSICS_INTERFACE_PHYSX
			bClearMeshes = !RuntimeCookPhysics_PhysX();
#elif WITH_CHAOS
			bClearMeshes = !RuntimeCookPhysics_Chaos();
#endif
		}
	}
	
	// fix up invalid transform to use identity
	// this can be here because BodySetup isn't blueprintable
	if ( GetLinkerUE4Version() < VER_UE4_FIXUP_BODYSETUP_INVALID_CONVEX_TRANSFORM )
	{
    
		for (int32 i=0; i<AggGeom.ConvexElems.Num(); ++i)
		{
    
			if ( AggGeom.ConvexElems[i].GetTransform().IsValid() == false )
			{
    
				AggGeom.ConvexElems[i].SetTransform(FTransform::Identity);
			}
		}
	}

#if WITH_CHAOS
	// For drawing of convex elements we require an index buffer, previously we could
	// get this from a PxConvexMesh but Chaos doesn't maintain that data. Instead now
	// it is a part of the element rather than the physics geometry, if we load in an
	// element without that data present, generate a convex hull from the convex vert
	// data and extract the index data from there.
	for(FKConvexElem& Convex : AggGeom.ConvexElems)
	{
    
		Convex.ComputeChaosConvexIndices();
	}
#endif


	if(bClearMeshes)
	{
    
		ClearPhysicsMeshes();
	}
	
	bCreatedPhysicsMeshes = true;

}

After looking at the code , Follow code , I found a solution .
1> First of all, you need to UStaticMesh Derive a class ;
2> And this kind of bAllowCPUAccess It has to be for true;
3> And reload GetWorld();
Then I'm adding one SetWorld Method ;
The specific code of this class is as follows :

/* *  from UStaticMesh Derived classes , Allow collision meshes to be cooked at runtime  *  Do that ,bAllowCPUAccess It has to be for true, And the way GetWorld() A valid... Must be returned world *  Otherwise, in the Cook There was a IsRuntime() My judgment is always false  */
UCLASS()
class EASYKITRUNTIMEMERGEMESH_API UEKRMM_RuntimeMesh : public UStaticMesh
{
    
	GENERATED_BODY()

public:
	UEKRMM_RuntimeMesh()
		: World(nullptr)
	{
    
		//  Set up bAllowCPUAccess by true, Allows you to copy rendered data triangles into the collision mesh  
		bAllowCPUAccess = true;
	}

	// UObject Cover 
	// Overrides allow cooking to collide with the mesh , Simple and complex , From the static grid at run time  
	virtual UWorld* GetWorld() const override {
     return World ? World : UStaticMesh::GetWorld(); }
	//  end UObject Cover 

	// Use an effective world , Allow collision meshes , Simple and complex , From the static grid at run time 
	void SetWorld(UWorld* InWorld) {
     World = InWorld; }

private:
	UWorld* World;
};

It's easy to use , as follows , And then I'll call UBodySetup->CreatePhysicsMeshes() Just OK 了 :

UEKRMM_RuntimeMesh* StaticMesh = NewObject< UEKRMM_RuntimeMesh >(GetTransientPackage(), MeshName, RF_Public | RF_Standalone);
if(!StaticMesh)
	continue;
StaticMesh->InitResources();
// The world must be set 
StaticMesh->SetWorld(RootActor->GetWorld());

difficulty 3,StaticMesh texture of material

Plug in encapsulation , Introduction to existing functions and subsequent plans

Features currently supported :
1> All of the same material mesh Merge together : Pass in a AActor Object as RootActor, To be able to RootActor All materials under the are the same UStaticmeshComponent Merge into a single UStaticMesh;
2> The material is correct
3> Ensure there are complex collisions
4> The coordinates are correct
5> To be added : Size calculation before merging , Block : The main purpose is to meet serialization and merge efficiency
6> To be added : Facet reduction plug-in , When merging, you can dynamically reduce faces , I'm going to get a plug-in out as well , Runtime subtraction algorithm
7> To be added :USkeletalMesh Of Merge
8> To be added : serialize

Reference article

Datasmith Runtime Official Blog
Unreal Engine 4.27 Datasmith Runtime Import
UE – StaticMesh analysis

<( ̄︶ ̄)> thank you , It's not easy to create , Great Xia, please stay … Move your lovely hands , Give me a compliment and then go !
原网站

版权声明
本文为[WhiteTian]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202130611315681.html