【UE4 C++】由点面数据,批量绘制ProceduralMesh并转化为StaticMesh资产

【UE4 C++】由点面数据,批量绘制ProceduralMesh并转化为StaticMesh资产_第1张图片 晒个丑陋的毕设截图

目录

一、功能需求

二、绘制结果

三、转化算法

3.1 灵感来源

3.2 算法难点

3.3 算法源码

四、绘制步骤

4.1 制作点面数据表格

4.2 处理存储表格数据

4.3  绘制并转换模型

4.4 重置化本地化ID

五、批量添加凸包碰撞


Github源工程:https://github.com/ColorGalaxy/UE4-Batch-Draw-Mesh-And-OpenGL-Get-Model-Data  

觉得赞,记得点Star

一、功能需求

本人做毕设时,想使用UE4进行练习,导师给的题目是矿山爆破模拟(后来觉得用Houdini做比较好,但当时执着于UE4的技能提升)。

他会提供给我9300块矿山爆破后的碎石模型数据,是使用C++(导师擅长opengl进行可视化)输出的13万顶点坐标三角面索引

我需要在UE4中重建该矿山模型,才能使用后续导师给的爆破速度数据。不使用可破坏网格体插件模拟也是出于各种原因。

因此研究了在UE4中批量绘制模型该问题。

后续:之后学习了Opengl,研究了如何输出模型的顶点与面索引,可参考下文。

【OpenGL C++ UE4】获取模型顶点及面索引数据,并优化存储结构供UE4绘制

二、绘制结果

【UE4 C++】由点面数据,批量绘制ProceduralMesh并转化为StaticMesh资产_第2张图片

虽说看着都很像,但是他们的轴心都在(0,0,0),放置在场景中就能拼凑成一个完整的矿山模型。 

三、转化算法

3.1 灵感来源

引擎是带有该功能的,但是仅支持在编辑器运行状态下,通过按钮点击才可以。

详见【UE4】ProceduralMeshComponent绘制自定义模型并转为StaticMesh该文章,有简单的教程。

因此,对引擎的源码进行了研究,找到了该功能的源码

3.2 算法难点

当时由于UE4 C++的开发经验不多,也没想到在引用头文件RawMesh.hProceduralMeshComponent.h时会报错,就连绝对路径都不行。一番折腾与苦找后,玄学解决了,但现在重试想弄个Demo的时候那个法子不行了....活见鬼系列。国外大佬还是多,如今找到了正确的解决办法,并做了总结,大家可以看一下。

【UE4 C++】RawMesh.h引用报错

【UE4 C++】无法打开源文件"ProceduralMeshComponent.h"的解决办法

3.3 算法源码

新建继承自Blueprint Function Library类的C++文件,用于编写将程序化模型转化为静态网格体的函数,暴露成节点给蓝图调用。鉴于担心刚入坑的萌新不知道复制在哪,我贴出了全部代码,剩余的都在蓝图中处理。

算法的核心就是提取Procedural Mesh中的信息,经由Raw Mesh将模型信息转入Static Mesh对象,最后AssetRegisterModule创建本地资源(目前还是临时资源,只存在于缓存中而不在本地磁盘中,关闭项目再次打开模型就不见了)。

  • PMConvertSM.h
#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "PMConvertSM.generated.h"

class UStaticMesh;
class UProceduralMeshComponent;
UCLASS()
class MINEBLAST_API UPMConvertSM : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
public:
	UFUNCTION(BlueprintCallable, Category = "Procedural Mesh Component")
		static UStaticMesh* ProceduralMeshConvertToStaticMesh(UProceduralMeshComponent* proMeshComp,FString outMeshName);//自定义绘制程序网格体转化为静态网格体
};
  • PMConvertSM.cpp
#include "PMConvertSM.h"

#include "ProceduralMeshComponent.h"
/*在项目.build.cs和.uproject的plugin下添加"ProceduralMeshComponent"解决头文件找不到路径*/
#include "Engine/StaticMesh.h"
#include "RawMesh/Public/RawMesh.h"
/*在项目.build.cs下添加"RawMesh"解决头文件找不到路径和编译错误问题*/
#include "AssetRegistryModule.h"

//改写自引擎源代码中的转化静态网格体按钮OnClickConvertToStaticMesh
UStaticMesh* UPMConvertSM::ProceduralMeshConvertToStaticMesh(UProceduralMeshComponent* proMeshComp,FString outMeshName)
{
	UProceduralMeshComponent * ProMesh = proMeshComp;
	if (ProMesh != nullptr)
	{
		FString PathName = FString(TEXT("/Game/Mesh/"));
		FString PackageName = PathName + outMeshName;

		//Raw mesh data we are filling in
		FRawMesh RawMesh;
		// Materials to apply to new mesh
		TArray MeshMaterials;

		const int32 NumSections = ProMesh->GetNumSections();
		int32 VertexBase = 0;
		for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++)
		{
			FProcMeshSection* ProSection = ProMesh->GetProcMeshSection(SectionIdx);

			// Copy verts
			for (FProcMeshVertex& Vert : ProSection->ProcVertexBuffer)
			{
				RawMesh.VertexPositions.Add(Vert.Position);
			}

			// Copy 'wedge' info
			int32 NumIndices = ProSection->ProcIndexBuffer.Num();
			for (int32 IndexIdx = 0; IndexIdx < NumIndices; IndexIdx++)
			{
				int32 Index = ProSection->ProcIndexBuffer[IndexIdx];

				RawMesh.WedgeIndices.Add(Index + VertexBase);

				FProcMeshVertex& ProcVertex = ProSection->ProcVertexBuffer[Index];

				FVector TangentX = ProcVertex.Tangent.TangentX;
				FVector TangentZ = ProcVertex.Normal;
				FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal() * (ProcVertex.Tangent.bFlipTangentY ? -1.f : 1.f);

				RawMesh.WedgeTangentX.Add(TangentX);
				RawMesh.WedgeTangentY.Add(TangentY);
				RawMesh.WedgeTangentZ.Add(TangentZ);

				RawMesh.WedgeTexCoords[0].Add(ProcVertex.UV0);
				RawMesh.WedgeColors.Add(ProcVertex.Color);
			}

			// copy face info
			int32 NumTris = NumIndices / 3;
			for (int32 TriIdx = 0; TriIdx < NumTris; TriIdx++)
			{
				RawMesh.FaceMaterialIndices.Add(SectionIdx);
				RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
			}

			// Remember material
			MeshMaterials.Add(ProMesh->GetMaterial(SectionIdx));

			// Update offset for creating one big index/vertex buffer
			VertexBase += ProSection->ProcVertexBuffer.Num();

			// If we got some valid data.
			if (RawMesh.VertexPositions.Num() > 3 && RawMesh.WedgeIndices.Num() > 3)
			{
				// Then find/create it.
				UPackage* Package = CreatePackage(NULL, *PackageName);
				check(Package);

				// Create StaticMesh object
				UStaticMesh* StaticMesh = NewObject(Package, FName(*outMeshName), RF_Public | RF_Standalone);
				StaticMesh->InitResources();

				StaticMesh->LightingGuid = FGuid::NewGuid();

				// Add source to new StaticMesh
				FStaticMeshSourceModel* SrcModel = new (StaticMesh->SourceModels) FStaticMeshSourceModel();
				SrcModel->BuildSettings.bRecomputeNormals = false;
				SrcModel->BuildSettings.bRecomputeTangents = false;
				SrcModel->BuildSettings.bRemoveDegenerates = false;
				SrcModel->BuildSettings.bUseHighPrecisionTangentBasis = false;
				SrcModel->BuildSettings.bUseFullPrecisionUVs = false;
				SrcModel->BuildSettings.bGenerateLightmapUVs = true;
				SrcModel->BuildSettings.SrcLightmapIndex = 0;
				SrcModel->BuildSettings.DstLightmapIndex = 1;
				SrcModel->SaveRawMesh(RawMesh);

				// Copy materials to new mesh
				for (UMaterialInterface* Material : MeshMaterials)
				{
					StaticMesh->StaticMaterials.Add(FStaticMaterial(Material));
				}

				//Set the Imported version before calling the build
				StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;

				// Build mesh from source
				StaticMesh->Build(false);
				StaticMesh->PostEditChange();

				// Notify asset registry of new asset
				FAssetRegistryModule::AssetCreated(StaticMesh);

				return StaticMesh;
			}
		}
	}
	return nullptr;
}
 

四、绘制步骤

4.1 制作点面数据表格

将顶点和三角面索引数据做成CSV格式的Excel表格,在UE中创建相同的表头结构体后,导入Excel做成数据表格

【UE4 C++】由点面数据,批量绘制ProceduralMesh并转化为StaticMesh资产_第3张图片

4.2 处理存储表格数据

通过表格中的0行分割不同的模型,将表格中所有模型的顶点和面片信息存入两个二维的数组中,蓝图详见Github Demo工程。

【UE4 C++】由点面数据,批量绘制ProceduralMesh并转化为StaticMesh资产_第4张图片

4.3  绘制并转换模型

调用Procedural Mesh ComponentCreate Mesh Section函数,输入顶点数组三角面数组创建模型。调用之前C++编写的模型类型转换节点,传入模型名称与Procedural Mesh组件,就会创建本地临时模型资源(能够在内容浏览器中看到,但是在系统文件夹中看不到)。

注:如果数据量过大,可能开始运行后,会卡住一段时间,但其实是在绘制的,比如我的毕设就将9300个模型分成了6组,每组1500个左右,绘制一组半个小时吧...(可能我电脑比较LJ)

【UE4 C++】由点面数据,批量绘制ProceduralMesh并转化为StaticMesh资产_第5张图片

4.4 重置化本地化ID

通过尝试,只有将模型的本地化ID重置,才能够重新保存,使模型保存在硬盘上。

【UE4 C++】由点面数据,批量绘制ProceduralMesh并转化为StaticMesh资产_第6张图片

这时保存所有,就能够在文件夹中看到这些资源了,下次打开项目依旧存在,它属于你啦。 

【UE4 C++】由点面数据,批量绘制ProceduralMesh并转化为StaticMesh资产_第7张图片

五、批量添加凸包碰撞

由于绘制的模型包围碰撞盒不够精确,而我的毕设需要碎石们模拟真实的碰撞滚动,物理这块用的是引擎自带的开启物理模拟。使用复杂碰撞无法开启物理模拟,因而逐一为9300个模型手动添加简单碰撞盒-凸包碰撞是不可能的,因而我们开启插件Editor Scripting Utilities,使用特殊的Actor在构造器中完成这一操作。

可以参考【UE4】如何使用Edit Scripting Utilities在蓝图中为大量静态网格体设置自动凸包碰撞

 

你可能感兴趣的:(UE4,UE4,c++,UE绘制模型,资产本地化,程序化模型转静态网格体,ProceduralMesh,自动凸包碰撞)