IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> UE4/UE5 Runtime下的Mesh合并 -> 正文阅读

[游戏开发]UE4/UE5 Runtime下的Mesh合并

原创文章,转载请注明出处。

前言

引擎版本:4.27.2
合并的前提:在UE4.26.2之后,允许了在运行时构件UStaticmesh。之前的方式只支持编辑器下导入,
导入后转成UStaticMesh的RenderData进行渲染;

为什么要做合并:

需求的来源是软件我们想利用UE4.27.2的runtime下的udatasmith导入功能,
但是因为datasmith的设计初衷呢就是尽可能小的拆分模型,粒度很小。
所以有的udatasmith导入后会在关卡中有上万个,帧率极低。
所以才会有了该篇文章 在运行时来合并StaticMesh。

合批的优化方案如下

其实这里面是有很多可以做合批的。
所以,这里我就针对udatasmith导入这个功能,研究了一下合批的方案;

方案优点缺点
1>修改datasmith导入部分插件的代码效率最高不易维护
2>自己做一套效率低于方案1,易维护易维护
3>修改datasmith的导出插件不确定不易维护

使用哪一种方案?

方案1来讲的话,改DataSmith的源码,效率我认为是最好的。
为什么:
首先说方案1的做法:在一个个的actor还没有spawn,mesh还没有build,collsion,材质等这些信息还没有build之前, 我们提前过滤好哪些Mesh可以被合并,之后再spawn MeshActor,build StaticMesh的vertex,collision,material。
再说方案2的做法:所有的actor已经在世界中spawn出来了,StaticMesh的vertex,collision,material这些信息都已经build好了。再去过滤好哪些Mesh可以被合并,之后再spawn MeshActor,build StaticMesh的vertex,collision,material。

暂时实现了方案2

对比看的话,方案1是要比方案2效率高的。但方案1改起来比较麻烦,并且我认为不容易维护。看了一天之后,我先实现了方案2。

视频效果:Merge之后的帧率和DC明显提高跳转观看

UE4/UE5 Runtime下合并Mesh

类图

在这里插入图片描述

Editor下的实现

参考MergeActor Tool
在这里插入图片描述

利用编辑器下的MergeActorTool工具的功能,很快就能在编辑器下实现合并的逻辑。Standlone下也可以合并。
但是需要注意的是这个只能在编辑器下用,打包就歇菜。
编辑器下合并具体的代码如下,作为参考:

//编辑器下的合并方法
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);
}

//合并具体逻辑,将相同材质的Mesh传进去即可完成合并。

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
	}
	
//删除被替代的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下的实现

难点1,StaticMesh的RenderData转FMeshDescription

其实这个如果看过StaticMesh的人应该了解,在编辑器下合并的代码都是用的编辑器下StaticMesh独有的数据来合并的,就是下面的图。用到的变量为 SourceModels
在这里插入图片描述
并且,编辑器下对StaticMesh的构建是最终会调用Build方法,但这些都在运行时无法使用。
我们需要使用引擎中新版本中的 BuildFromStaticMeshDescriptions来生成UStaticMesh。
在这里插入图片描述
BuildFromStaticMeshDescriptions该方法需要的是FMeshDescription,FMeshDescription在编辑器下导入之后就有了,但是运行时UStaticMesh的SourceModels不存在了,怎么办?
我们需要反推,最终渲染的数据都存在UStaticMesh的RenderData中,所以我们就从RenderData里面把数据转成FMeshDescription数组就好了。

依次将每一个可以合并的Mesh的数据从RenderData转换成FMeshDescription,接着再将这些
FMeshDescription加到一次,再给到UStaticMesh的BuildFromStaticMeshDescriptions传进去就搞定了(此处需要注意数据的大小,UE的序列化不能超2G,但是好在这块都是我们自己写,再拼接FMeshDescription的时候我们把内存控制好就行了,这块也关系到合并的速度)

具体步骤概括一下其实就是:
1>RenderData转FMeshDescription
2>拼接所有的FMeshDescription
3>调用BuildFromStaticMeshDescriptions

难点2,StaticMesh构建复杂碰撞

要构建复杂碰撞,那么就要调用
UBodySetup->CreatePhysicsMeshes(),如果仔细跟过的话,进去后会发现,在Runtime下,build碰撞会调用ProcessFormatData_PhysX或者ProcessFormatData_Chaos,但是前提条件必须满足IsRuntime的判断。
我发现这块的原因就是,合并好之后,我在创建UStaticMesh对象的时候写法就是普通的,NewObect(xxxxxxx),结果在IsRuntime的判断那里一直为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))//这个地方在Runtime下如果你用的是UStaticMesh的话,是无法通过的。
		{
#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;

}

经过看代码,跟代码,我找到了解决方案。
1>首先需要从UStaticMesh派生一个类出来;
2>并且这个类的bAllowCPUAccess必须为true;
3>并且要重载一下GetWorld();
然后自己在加一个SetWorld方法;
这个类的具体代码如下 :

/*
* 从UStaticMesh派生的类,允许在运行时烹饪碰撞网格
* 要做到这一点,bAllowCPUAccess必须为true,并且方法GetWorld()必须返回一个有效的world
* 否则在Cook的时候有个IsRuntime()的判断是一直为假
*/
UCLASS()
class EASYKITRUNTIMEMERGEMESH_API UEKRMM_RuntimeMesh : public UStaticMesh
{
	GENERATED_BODY()

public:
	UEKRMM_RuntimeMesh()
		: World(nullptr)
	{
		// 设置bAllowCPUAccess为true,允许将渲染数据三角形复制到碰撞网格中  
		bAllowCPUAccess = true;
	}

	// UObject覆盖
	//覆盖允许烹饪碰撞网格,简单和复杂,从静态网格在运行时  
	virtual UWorld* GetWorld() const override { return World ? World : UStaticMesh::GetWorld(); }
	// 结束UObject覆盖

	//使用有效的世界,允许碰撞网格烹饪,简单和复杂,从静态网格在运行时
	void SetWorld(UWorld* InWorld) { World = InWorld; }

private:
	UWorld* World;
};

用法就比较简单了,如下,之后再去调用UBodySetup->CreatePhysicsMeshes()就OK了:

UEKRMM_RuntimeMesh* StaticMesh = NewObject< UEKRMM_RuntimeMesh >(GetTransientPackage(), MeshName, RF_Public | RF_Standalone);
if(!StaticMesh)
	continue;
StaticMesh->InitResources();
//必须设置世界
StaticMesh->SetWorld(RootActor->GetWorld());

难点3,StaticMesh材质

插件封装,现有功能介绍以及后续计划

目前支持的功能:
1>所有相同材质的mesh合并到一起:传入一个AActor对象作为RootActor,能够将RootActor下的所有材质相同的UStaticmeshComponent合并成单个UStaticMesh;
2>材质正确
3>保证有复杂碰撞
4>坐标正确
5>待添加:合并之前的大小计算,分块:主要目的是满足序列化以及兼顾合并效率
6>待添加:减面插件,合并时候可以动态减面,我准备同样弄一个插件出来,运行时的减面算法
7>待添加:USkeletalMesh的Merge
8>待添加:序列化

参考文章

Datasmith Runtime 官方的Blog
Unreal Engine 4.27 Datasmith Runtime Import
UE – StaticMesh 分析

<( ̄︶ ̄)>谢谢,创作不易,大侠请留步… 动起可爱的双手,来个赞再走吧!
  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-02-14 21:31:23  更:2022-02-14 21:33:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 16:48:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码