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

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

😊觉得赞,记得点Star⭐

目录

一、功能需求

二、成果

三、环境配置

四、详细步骤

4.1 Max制作三棱锥并处理

4.2 核心代码

4.2.1 传入结构体数据

4.2.2 顶点去重、更新索引

4.2.3 输出本地CSV文件

4.3 UE4绘制


一、功能需求

想必你肯定会问我一个问题,UE4直接导入模型不好么?

哈哈,前提是在做毕设时,导师提供的只有顶点与面索引数据,没有模型。

下文详细介绍了毕设开发中的难点,涉及三篇其他文章。

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

后来学习了《LearnOpenGL模型加载》一章后,能够通过Assimp库读取obj模型并在窗口中绘制了。

因此十分好奇,我能否也可以经由C++输出与毕设相同格式的模型顶点与面片数据集

二、成果

1.输出的CSV表格在MeshData文件夹下,比如这是个简单的三棱锥(方便调试查找BUG)输出的点面数据集。

Vertices
Triangles

2. 3ds max建一个茶壶,通过Opengl窗口绘制显示,然后输出点面数据道CSV表格,最后导入UE4中绘制。

3ds max中的茶壶
Opengl窗口绘制——线框显示

 

UE4绘制的静态网格体——线框显示

三、环境配置

我将该项目所需的第三方库文件GLFWGLADASSIMPstb_image.h均放在了项目中,并设置了相对路径防止路径丢失。

若有疑问或想尝试,也可去LearnOpenGL网址学习自行配置。

四、详细步骤

4.1 Max制作三棱锥并处理

用自带的工具栏新建简单的三棱锥,转换为可编辑多边形,可以看到顶点、边还是很多。

通过边删除、点去除、焊接、边界封口,去掉多余的边、顶点,转化成一个只包含四个顶点,四个面的简单三棱锥用于测试。 

导出为obj,使用VS工程,设置模型路径,输出模型的顶点、面索引数据。 

4.2 核心代码

VS项目文件结构

如何读取模型,学习《LearnOpenGL模型加载》,此处不再赘述,主要对重复顶点数据去除、索引更新、打印CSV做说明。

4.2.1 传入结构体数据

Debug模式可以看到三棱锥的点面数据,有很多顶点是重复的,并且与面索引是一一对应的。

UE4中使用Procedural Mesh绘制只需要传入不同的顶点,因此简化数据,进行顶点去重、更新索引

三棱锥的顶点位置
三棱锥的面索引

 在Mesh.h的构造器中,将Vertex结构体中的position传入到MeshInfoCSV.h文件中进行处理,面索引是自动递增的,直接传入i值,然后调用Export函数打印。

struct Vertex//Assimp读数据的结构体
{
	glm::vec3 position;
	glm::vec3 normal;
	glm::vec2 texCoords;
};
struct outData//自定义进行去重操作的结构体
{
	float verticesPos_X;
	float verticesPos_Y;
	float verticesPos_Z;
	unsigned int index;
	unsigned int initialIndex;
};
Mesh(vector<Vertex> vertices, vector<unsigned int> indices, vector<Texture> textures)
{
	this->vertices = vertices;
	this->indices = indices;
	this->textures = textures;

#ifdef EXPORT_MODEL_DATA
	vector<outData> allInfo;
	outData info;
	for (int i = 0; i < vertices.size(); i++)
	{
		info.verticesPos_X = vertices[i].position.x;
		info.verticesPos_Y = vertices[i].position.y;
		info.verticesPos_Z = vertices[i].position.z;
		info.index = i;
		info.initialIndex = i;
		allInfo.push_back(info);
	}
	MeshInfoCSV exportTxt(allInfo);
	exportTxt.ExportVertices();
	exportTxt.ExportTriangles();
#endif
	setupMesh();
}

4.2.2 顶点去重、更新索引

思想:将所有的顶点座标从小到大进行排序。面索引更新的原则是遍历所有顶点结构体成员时,若当前项与后一项顶点重复,allInfo结构体中去除后一项,并根据记录后一项的初始面索引,找到需要更新的新索引数组的下标,更新其值为当前项的顶点序号;若后一项不重复,就将顶点序号增加,再更新后一项的新索引

最终得到的结果就是不同的顶点座标结构体allInfo与更新的面索引数组indices。

MeshInfoCSV(vector<outData> allInfo)
{
	this->allInfo = allInfo;
	//initial indices
	for (int i = 0; i < allInfo.size(); i++)
		this->indices.push_back(i);
	OptimizeData();
}

//remove duplicate vertex and modify indices
void OptimizeData()
{
	//sort by position's value for remove the duplicate index
	sort(allInfo.begin(), allInfo.end(), cmpByPosition);

	//compare with previous data
	//将重复的顶点去掉,更新面片的索引下标为不重复的顶点下标(内存优化)
	if (allInfo.size() > 1)
	{
		int newIndex = 0;
		allInfo[0].index = newIndex;
		indices[allInfo[0].initialIndex] = newIndex;
		int len = allInfo.size();
		for (int i = 0; i < len - 1; i++)
		{
			if (allInfo[newIndex].verticesPos_X == allInfo[newIndex + 1].verticesPos_X 
			&& allInfo[newIndex].verticesPos_Y == allInfo[newIndex + 1].verticesPos_Y
			&& allInfo[newIndex].verticesPos_Z == allInfo[newIndex + 1].verticesPos_Z)
			{//update repetitive vertex's indexa
				allInfo[newIndex + 1].index = newIndex;
				indices[allInfo[newIndex + 1].initialIndex] = newIndex;
				allInfo.erase(allInfo.begin() + newIndex + 1);
			}
			else
			{
				newIndex++;
				indices[allInfo[newIndex].initialIndex] = newIndex;
				allInfo[newIndex].index = newIndex;
			}
		}
	}
}

4.2.3 输出本地CSV文件

fopen会提示不安全,要用fopen_s打开文件,使用w+模式可以覆盖原文件内容。

void ExportVertices()
{	
	int i;
	FILE *fp;
	//please keep close excel when print to file
	fopen_s(&fp,"MeshData/OpenglVertices.csv", "w+");//model has one mesh
	fprintf(fp, ",MeshID,Vertice_X,Vertice_Y,Vertice_Z\n");
	for (i = 0; i < allInfo.size(); i++)
	{
		fprintf(fp, "%d,1,%.2f,%.2f,%.2f\n", i+1,allInfo[i].verticesPos_X, allInfo[i].verticesPos_Y, allInfo[i].verticesPos_Z);
	}
	fprintf(fp, "%d,0,0,0,0\n",i+1);
	fclose(fp);
	cout << "Vertices successfully print to csv excel!" << endl;
}

打印面索引的顺序必须是indices[i], indices[i+2], indices[i+1],因为要保证三个顶点是顺时针顺序排列,否则在UE4中绘制得到的面就是反向的,会造成三棱锥外表面不可见,内表面可见的结果。

void ExportTriangles()
{
	int i;
	FILE *fp;
	fopen_s(&fp, "MeshData/OpenglTriangles.csv", "w+");//model has one mesh
	fprintf(fp, ",MeshID,Vertice1,Vertice2,Vertice3\n");
	for (i = 0; i < indices.size(); i+=3)
	{
		//Vertex order must be clockwise in ue4 draw
		fprintf(fp, "%d,1,%d,%d,%d\n", i/3 + 1, indices[i], indices[i+2], indices[i+1]);
	}
	fprintf(fp, "%d,0,0,0,0\n", i/3 + 1);
	fclose(fp);
	cout << "Triangles successfully print to csv excel!" << endl;
}

4.3 UE4绘制

得到顶点、三角面索引CSV文件后,结合该文章【UE4 C++】由点面数据,批量绘制ProceduralMesh并转化为StaticMesh资产就能在UE4中绘制得到模型了。目前该VS项目仅适用于单个模型(只包含一个Mesh)的数据集输出,不能同时传入多个模型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章