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

28 篇文章 11 订阅
晒个丑陋的毕设截图

目录

一、功能需求

二、绘制结果

三、转化算法

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绘制

二、绘制结果

虽说看着都很像,但是他们的轴心都在(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<UMaterialInterface*> 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<UStaticMesh>(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做成数据表格

4.2 处理存储表格数据

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

4.3  绘制并转换模型

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

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

4.4 重置化本地化ID

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

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

五、批量添加凸包碰撞

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

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

 

  • 22
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
### 回答1: UE4静态网格材质是指应用于静态网格的材质。静态网格是由顶点、多边形和顶点颜色构成的三维模型静态网格材质负责定义这些三维模型的外观和表面属性。 UE4中,可以通过将材质应用到静态网格上,来改变其外观。静态网格材质可以包含各种着色属性,如颜色、纹理、法线贴图、金属度、粗糙度等。通过调整这些属性的数值或导入自定义图片,可以实现不同材质效果,如金属、木材、石头、草地等。 在UE4中创建一个静态网格材质,首先需要在材质编辑器中创建一个新的材质实例。然后,可以通过添加各种材质节点来定义不同的属性。例如,添加一个颜色节点可以调整模型的基础颜色,添加一个纹理节点可以贴上自定义的贴图来改变模型的表面纹理。 在应用材质到静态网格时,可以通过在级别编辑器中选择网格并将创建的材质实例拖放到这个网格上来实现。一个静态网格可以应用多个材质,通过在编辑器中选择其不同部分,可以分别给不同的部分应用不同的材质。 总之,UE4静态网格材质是用于定义静态网格外观和表面属性的工具,通过调整不同属性的数值或导入自定义贴图,可以实现各种不同的材质效果。 ### 回答2: UE4中的Static Mesh是指由顶点和面构成的静态模型。而材质是用来渲染物表面的一种方式。在UE4中,我们可以给Static Mesh添加材质来改变其表面的外观和质感。 首先,我们需要创建一个材质实例来设置Static Mesh的材质。在UE4中,我们可以使用材质编辑器来创建和编辑材质。在材质编辑器中,我们可以添加各种节点来控制Static Mesh的纹理、颜色、高光、法线贴图等属性。 然后,将创建好的材质实例应用到Static Mesh上。可以通过选中Static Mesh,在详细面板的材质选项中选择已创建的材质实例。这样就可以将材质应用到Static Mesh上,并在场景中进行预览。 另外,UE4还提供了一些可以控制Static Mesh材质的属性。比如,可以调整材质的透明度、金属度、粗糙度等参数,以达到更好的渲染效果。此外,还可以使用材质实例中的参数来实现动态材质的效果,比如动态的纹理滚动、颜色变换等。 总之,UE4中的Static Mesh材质功能非常强大。通过选择合适的材质实例并调整对应的属性,我们可以使Static Mesh在游戏中呈现出各种不同的外观和效果,增强游戏的视觉效果和沉浸感。
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值