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();//循環執行
}

在這裏插入圖片描述

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