OpenGL读取obj文件模型

网上找了半天类似的程序,愣是没找到,后来干脆自己动手造轮子,google学了一通之后写了一个示例程序,简单易懂

Posted by ChenJY on December 7, 2016 | Viewed times

最近在学习 OpenGL 如何读入 obj模型 的时候,发现百度出来,相应的示例程序要么跑不通、要么较复杂,初学者的我想找到个简单易懂的示例程序愣是没找到。只好自己通过谷歌学习之后,写了个简单的示例程序,实现了基本的读入 obj模型 的功能,希望对于初学 OpenGL 的朋友有一些帮助。

第一,配置VS2015 + OpenGL开发环境

这里我是参考的简书作者 @fan2b直通车,这位作者写的非常详细,而且配置出来的程序能直接整个工程拷走运行,很赞

第二,开始正题

1、新建 ObjLoader.h
#include "Dependencies\glew\glew.h"
#include "Dependencies\freeglut\freeglut.h"
#include <vector>
#include <string>
using namespace std;
class ObjLoader{
public:
	ObjLoader(string filename);//构造函数
	void Draw();//绘制函数
private:
	vector<vector<GLfloat>>vSets;//存放顶点(x,y,z)坐标
	vector<vector<GLint>>fSets;//存放面的三个顶点索引
};
2、新建ObjLoader.cpp
#include "ObjLoader.h"
#include <fstream>
#include <iostream>
using namespace std;

ObjLoader::ObjLoader(string filename)
{
	string line;
	fstream f;
	f.open(filename, ios::in);
	if (!f.is_open()){
		cout << "Something Went Wrong When Opening Objfiles" << endl;
	}

	while (!f.eof()){
		getline(f, line);//拿到obj文件中一行,作为一个字符串
		vector<string>parameters;
		string tailMark = " ";
		string ans = "";

		line = line.append(tailMark);
		for (int i = 0; i < line.length(); i++) {
			char ch = line[i];
			if (ch != ' ') {
				ans+=ch;
			}
			else {
				parameters.push_back(ans); //取出字符串中的元素,以空格切分
				ans = "";
			}
		}
		//cout << parameters.size() << endl;
		if (parameters.size() != 4) {
			cout << "the size is not correct" << endl;
		}
		else {
			if (parameters[0] == "v") {   //如果是顶点的话
				vector<GLfloat>Point;
				for (int i = 1; i < 4; i++) {   //从1开始,将顶点的xyz三个坐标放入顶点vector
					GLfloat xyz = atof(parameters[i].c_str());
					Point.push_back(xyz);
				}
				vSets.push_back(Point);
			}

			else if (parameters[0] == "f") {   //如果是面的话,存放三个顶点的索引
				vector<GLint>vIndexSets;
				for (int i = 1; i < 4; i++){
					string x = parameters[i];
					string ans = "";
					for (int j = 0; j < x.length(); j++) {   //跳过‘/’
						char ch = x[j];
						if (ch != '/') { 
							ans += ch;
						}
						else {
							break;
						}
					}
					GLint index = atof(ans.c_str());
					index = index--;//因为顶点索引在obj文件中是从1开始的,而我们存放的顶点vector是从0开始的,因此要减1
					vIndexSets.push_back(index);
				}
				fSets.push_back(vIndexSets);
			}
		}
	}
	f.close();
}

void ObjLoader::Draw(){

	glBegin(GL_TRIANGLES);//开始绘制
	for (int i = 0; i < fSets.size(); i++) {
		GLfloat VN[3];
		//三个顶点
		GLfloat SV1[3];
		GLfloat SV2[3];
		GLfloat SV3[3];

		if ((fSets[i]).size() != 3) {
			cout << "the fSetsets_Size is not correct" << endl;
		}
		else {
				GLint firstVertexIndex = (fSets[i])[0];//取出顶点索引
				GLint secondVertexIndex = (fSets[i])[1];
				GLint thirdVertexIndex = (fSets[i])[2];

				SV1[0] = (vSets[firstVertexIndex])[0];//第一个顶点
				SV1[1] = (vSets[firstVertexIndex])[1];
				SV1[2] = (vSets[firstVertexIndex])[2];

				SV2[0] = (vSets[secondVertexIndex])[0]; //第二个顶点
				SV2[1] = (vSets[secondVertexIndex])[1];
				SV2[2] = (vSets[secondVertexIndex])[2];

				SV3[0] = (vSets[thirdVertexIndex])[0]; //第三个顶点
				SV3[1] = (vSets[thirdVertexIndex])[1];
				SV3[2] = (vSets[thirdVertexIndex])[2];


				GLfloat vec1[3], vec2[3], vec3[3];//计算法向量
				//(x2-x1,y2-y1,z2-z1)
				vec1[0] = SV1[0] - SV2[0];
				vec1[1] = SV1[1] - SV2[1];
				vec1[2] = SV1[2] - SV2[2];

				//(x3-x2,y3-y2,z3-z2)
				vec2[0] = SV1[0] - SV3[0];
				vec2[1] = SV1[1] - SV3[1];
				vec2[2] = SV1[2] - SV3[2];

				//(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(SV1[0], SV1[1], SV1[2]);//绘制三角面片
				glVertex3f(SV2[0], SV2[1], SV2[2]);
				glVertex3f(SV3[0], SV3[1], SV3[2]);	
		}
	}
	glEnd();
}
3、最后,新建main.cpp
#include "ObjLoader.h"
using namespace std;
//模型路径
string filePath = "data/monkey.obj";

ObjLoader objModel = ObjLoader(filePath);
//实现移动鼠标观察模型所需变量
static float c = 3.1415926 / 180.0f;
static float r = 1.0f;
static int degree = 90;
static int oldPosY = -1;
static int oldPosX = -1;

//安置光源
void setLightRes() {
	GLfloat lightPosition[] = { 0.0f, 0.0f, 1.0f, 0.0f };
	glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
	glEnable(GL_LIGHTING); //启用光源
	glEnable(GL_LIGHT0);   //使用指定灯光
}

//初始化
void init() {
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(500, 500);
	glutCreateWindow("ObjLoader");
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_SMOOTH);
	setLightRes();
	glEnable(GL_DEPTH_TEST);
}

void display()
{
	glColor3f(1.0, 1.0, 1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW);                            
	glLoadIdentity();                                     
	glTranslatef(0.0f, 0.0f, -5.0f);              												
	setLightRes();
	glPushMatrix();

	gluLookAt(r*cos(c*degree), 0, r*sin(c*degree), 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

	objModel.Draw();//绘制obj模型
	glPopMatrix();
	glutSwapBuffers();
}

void reshape(int width, int height)
{
	glViewport(0, 0, width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.0f, (GLdouble)width / (GLdouble)height, 1.0f, 200.0f);
	glMatrixMode(GL_MODELVIEW);
}

//移动鼠标360观察模型
void moseMove(int button, int state, int x, int y)
{
	if (state == GLUT_DOWN) {
		oldPosX = x; oldPosY = y;
	}
}
void changeViewPoint(int x, int y)
{
	int temp = x - oldPosX;
	degree += temp;
	oldPosX = x;
	oldPosY = y;
}

void myIdle()
{
	glutPostRedisplay();
}

int main(int argc, char* argv[])
{
	glutInit(&argc, argv);
	init();
	glutDisplayFunc(display);
	glutReshapeFunc(reshape);
	glutMouseFunc(moseMove);
	glutMotionFunc(changeViewPoint);
	glutIdleFunc(myIdle);
	glutMainLoop();
	return 0;
}

总结

这份代码比较小白,也是我初学的水平,但好在比较简单易懂,针对复杂的 obj文件 读取,比如说顶点法向量和纹理的载入等,可在之基础上进行改进。整个工程可在此下载

运行效果:

运行效果图

许可协议


这是一个不定时更新的、披着程序员外衣的文青小号。

在这里,既分享极客技术,也记录人间烟火,欢迎关注。


Comment