OpenGL glut导入OBJ模型文件

程序环境

IDE:Visual Stdio 2019
语言:C++
OpenGL库:glut
不会安装glut环境的看我上一篇博客

什么是OBJ文件

obj文件是3D模型文件格式。由Alias|Wavefront公司为3D建模和动画软件"Advanced Visualizer"开发的一种标准,适合用于3D软件模型之间的互导,也可以通过Maya读写。
OBJ文件是一种文本文件,可以直接用写字板打开进行查看和编辑修改。

使用Blender建模,得到一个猴头模型
在这里插入图片描述
导出为obj文件
在这里插入图片描述
得到一个obj文件,右键用打开方式选TXT
在这里插入图片描述
在这里插入图片描述

OBJ文件格式

上面导出时,我选择了三角面,所以在obj文件中f开头那一行数据只有三个数字,这三个是顶点索引。之所以这样做是为了下面方便写导入函数。
具体的格式:看这篇博客

实现思路

众所周知,不共线的3个点可以确定一个平面。在OpenGL中,绘制一个面需要3个顶点座标,绘制方向是逆时针。
在这里插入图片描述

读取OBJ文件的数据

由于obj文件的行数十分多,光一个只包含顶点数据顶点索引的猴头就有1500行,所以要采用c++的vector来储存数据。读取文件少不了要处理字符串,还好c++提供了十分友好的字符串处理函数fstreamsstream
为了方便操作,我写成一个ObjLoader类。

#pragma once
#include <vector>
#include <iostream>
#include <gl/glut.h>

using namespace std;

class ObjLoader
{
public:
	struct vertex
	{
		float x;
		float y;
		float z;
	};
	ObjLoader(string filename);//读取函数
	void Draw();//绘制函数
private:
	vector<vector<GLfloat>> v;//存放顶点(x,y,z)座标
	vector<vector<GLint>> f;//存放面的三个顶点索引
};
ObjLoader::ObjLoader(string filename)
{
	ifstream file(filename);
	string line;
	while (getline(file, line))
	{
		if (line.substr(0, 1) == "v")
		{
			vector<GLfloat> Point;
			GLfloat x, y, z;
			istringstream s(line.substr(2));
			s >> x; s >> y; s >> z;
			Point.push_back(x);
			Point.push_back(y);
			Point.push_back(z);
			v.push_back(Point);

		}
		else if (line.substr(0, 1) == "f")
		{
			vector<GLint> vIndexSets;
			GLint u, v, w;
			istringstream vtns(line.substr(2));
			vtns >> u; vtns >> v; vtns >> w;
			vIndexSets.push_back(u - 1);
			vIndexSets.push_back(v - 1);
			vIndexSets.push_back(w - 1);
			f.push_back(vIndexSets);
		}
	}
	file.close();
}

我的代码是基于这篇博客的框架改过的,只适应于f只含有3个顶点索引的obj文件。原作者的代码也不是万能的,照抄会发生vector越界。因为obj文件内容不同,读取不对应的obj文件必出错。

绘制模型

绘制的思路也不难。首先声明3个顶点,然后读取顶点数据,再用glVertex3f函数导入。至于这里计算法线,只是为了我后面使用光照渲染得更逼真而已,不需要渲染的可以去掉相关语句。

void ObjLoader::Draw()
{
	glBegin(GL_TRIANGLES);//开始绘制
	for (int i = 0; i < f.size(); i++) {
		GLfloat VN[3];//法线
		//三个顶点
		vertex a, b, c, normal;

		if ((f[i]).size() != 3) {
			cout << "ERRER::THE SIZE OF f IS NOT 3!" << endl;
		}
		else {
			GLint firstVertexIndex = (f[i])[0];//取出顶点索引
			GLint secondVertexIndex = (f[i])[1];
			GLint thirdVertexIndex = (f[i])[2];

			a.x = (v[firstVertexIndex])[0];//第一个顶点
			a.y = (v[firstVertexIndex])[1];
			a.z = (v[firstVertexIndex])[2];

			b.x = (v[secondVertexIndex])[0]; //第二个顶点
			b.y = (v[secondVertexIndex])[1];
			b.z = (v[secondVertexIndex])[2];

			c.x = (v[thirdVertexIndex])[0]; //第三个顶点
			c.y = (v[thirdVertexIndex])[1];
			c.z = (v[thirdVertexIndex])[2];


			GLfloat vec1[3], vec2[3], vec3[3];//计算法向量
			//(x2-x1,y2-y1,z2-z1)
			vec1[0] = a.x - b.x;
			vec1[1] = a.y - b.y;
			vec1[2] = a.z - b.z;

			//(x3-x2,y3-y2,z3-z2)
			vec2[0] = a.x - c.x;
			vec2[1] = a.y - c.y;
			vec2[2] = a.z - c.z;

			//(x3-x1,y3-y1,z3-z1)
			vec3[0] = vec1[1] * vec2[2] - vec1[2] * vec2[1];
			vec3[1] = vec2[0] * vec1[2] - vec2[2] * vec1[0];
			vec3[2] = vec2[1] * vec1[0] - vec2[0] * vec1[1];

			GLfloat D = sqrt(pow(vec3[0], 2) + pow(vec3[1], 2) + pow(vec3[2], 2));

			VN[0] = vec3[0] / D;
			VN[1] = vec3[1] / D;
			VN[2] = vec3[2] / D;

			glNormal3f(VN[0], VN[1], VN[2]);//绘制法向量

			glVertex3f(a.x, a.y, a.z);//绘制三角面
			glVertex3f(b.x, b.y, b.z);
			glVertex3f(c.x, c.y, c.z);
		}
	}
	glEnd();
}

实战效果

声明为全局变量,在绘制窗口函数中调用

ObjLoader monkey= ObjLoader("monkey.obj");
void display(void)
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glDepthFunc(GL_LESS);
	glEnable(GL_DEPTH_TEST);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);//像素传输
	setLight();//渲染光照
	monkey.Draw();
}
void main(int argc, char** argv)
{
	InitWindow(argc, argv);//执行初始化程序
	glutDisplayFunc(&display);//将图形信息送往窗口显示
	glClear(GL_COLOR_BUFFER_BIT);
	glutMainLoop();//循环执行
}

在这里插入图片描述

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