OpenGL渲染obj文件

obj是常见的3D模型文件格式,如何使用OpenGL读取obj文件并渲染出来呢?

工具

  • CodeBlocks
  • GLUT

CodeBlocks中使用GLUT

这是一个新手常见的问题,首先你需要去网上下载一个glut包,然后将解压后的文件按照下面的步骤配置:
32 位系统:

  • 拷贝glut32.dll 到c:\windows\system
  • 拷贝glut32.lib到c:\program files\mingw\lib
  • 拷贝glut.h 到c:\program files\mingw\include\GL

64位系统:

  • 拷贝glut.dll 到c:\windows\SysWOW64
  • 拷贝glut.lib到c:\program files\mingw\lib
  • 拷贝glut.h 到c:\program files\mingw\include\GL

很多时候,虽然我是64位系统,但是还需要把32位系统的也配置一遍,也许是因为我的CodeBlocks是32位的…

配置完成后,打开CodeBlocks新建一个项目,项目类型选择时,选GLUT project即可,创建完成后,直接编译运行。如果报下面这个错:

undefined reference to _imp__glViewport  

在main.cpp文件头加上这句:

#include <windows.h>

或者下面这个;

#define _STDCALL_SUPPORTED

如果不出意外的话,就可以运行这个demo了:
OpenGL Demo

读取obj文件

首先我们要知道obj文件格式。obj文件格式比较复杂,但是渲染出简单的物体,我们只需要知道两个:顶点v和面f。下面是teapot.obj的一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
v -6.5 12 0
v -2.9288 12.75 -2.9288
v -4.615 12 -4.615
v 0 12.75 -4.125
v 0 12 -6.5
v 2.9288 12.75 -2.9288
v 4.615 12 -4.615
f 1 2 3
f 1 3 4
f 4 3 5
f 4 5 6
...

最简单的格式:

  • v -6.5 12 0:v开头的这一行表示一个顶点,三维坐标为(-6.5,12,0)
  • f 1 2 3:f开头的这一行表示一个面,这个面有三个顶点,这三个顶点的索引值分别为 1,2,3

索引值就是顶点在文件里的顺序,从1开始。
我们现在可以设计一个数据结构来表示obj了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 几何体顶点
typedef struct Vertex
{
float x;
float y;
float z;
} Vertex;
// 面(假设一个面只有三个顶点)
typedef struct Face
{
int a;
int b;
int c;
}Face;
class Obj
{
public:
Obj(){};
~Obj(){};
void addVertex(Vertex vertex);
void addFace(Face face);
Vertex getVertex(int index);
Face getFace(int index);
int getFacesNum();
private:
vector<Vertex> vertexs;
vector<Face> faces;
};

逐行读取obj文件,然后根据行首的关键字,做不同的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#ifndef OBJ_READER_H
#define OBJ_READER_H
#include "obj.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
class ObjFile
{
public:
ObjFile(){}
~ObjFile(){}
inline bool openObjFile(const string & filename);
inline void closeObjFile();
inline bool readObjFile(Obj & obj, const string & filename);
inline void parseReadingState(const string & keyWord);
inline void dispatchReading(Obj & obj, istringstream & line);
void readOneLine(char * buf, unsigned int bufSize);
void processComment(istringstream & line);
void processV(Obj & obj, istringstream & line);
void processF(Obj & obj, istringstream & line);
private:
enum READ_STATE { COMMENT, V , F , NULL_LINE};
ifstream inFile;
READ_STATE readState;
};
inline bool ObjFile::openObjFile(const string & filename)
{
inFile.open(filename.c_str());
if(!inFile) // fail to open
return false;
else
return true;
}
inline bool ObjFile::readObjFile(Obj & obj, const string & filename)
{
if (!openObjFile(filename)) // open error
return false;
unsigned int bufSize = 1024;
char * buf = new char [bufSize];
string keyWord;
while(!inFile.eof())
{
readOneLine(buf, bufSize);
istringstream line(buf);
keyWord.clear();
line >> keyWord;
parseReadingState(keyWord);
line.seekg(0, ios_base::beg); // discarding the read keyWord
line.clear();
dispatchReading(obj, line);
}
return true;
}
inline void ObjFile::closeObjFile()
{
inFile.close();
}
inline void ObjFile::parseReadingState(const string & keyWord)
{
if (keyWord == "") // skip null line
{
readState = NULL_LINE;
return;
}
else if (keyWord.c_str()[0] == '#') // comment
{
readState = COMMENT;
}
else if (keyWord == "v")
{
readState = V;
}
else if (keyWord == "f")
{
readState = F;
}else{ // Other lines are treated as comments
readState = COMMENT;
}
}
inline void ObjFile::dispatchReading(Obj & obj, istringstream & line)
{
switch (readState)
{
case COMMENT:
processComment(line);
break;
case V:
processV(obj, line);
break;
case F:
processF(obj, line);
break;
default:
break;
} // end switch
}
void ObjFile::readOneLine(char * buf, unsigned int bufSize)
{
inFile.getline(buf,bufSize);
}
void ObjFile::processComment(istringstream & line)
{
string commentLine;
getline(line, commentLine);
cout << commentLine.c_str() << endl;
}
// process Vertex
void ObjFile::processV(Obj & obj, istringstream & line)
{
string v;
line >> v;
float x,y,z;
line >> x >> y >> z;
Vertex vertex = {x,y,z};
obj.addVertex(vertex);
}
// process Face
void ObjFile::processF(Obj & obj, istringstream & line)
{
string f;
int a,b,c;
line >> f;
line >> a >> b >> c;
Face face = {a,b,c};
obj.addFace(face);
}
#endif OBJ_READER_H

渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glPushMatrix();
glTranslatef(transx, -transy, transz);
glRotatef(roty, 1.0f, 0.0f, 0.0f);
glRotatef(rotx, 0.0f, 1.0f, 0.0f);
int faces_count = obj.getFacesNum();
for(int i=0; i<faces_count; i++)
{
Face face = obj.getFace(i);
// draw wireframe
if(is_Wireframe)
glBegin(GL_LINE_LOOP);
else
glBegin(GL_TRIANGLES);
// glColor3f(1.0f,0.0f,0.0f);
Vertex v1 = obj.getVertex(face.a-1);
float x1 = v1.x;
float y1 = v1.y;
float z1 = v1.z;
glVertex3f(x1,y1,z1);
Vertex v2 = obj.getVertex(face.b-1);
float x2 = v2.x;
float y2 = v2.y;
float z2 = v2.z;
glVertex3f(x2,y2,z2);
Vertex v3 = obj.getVertex(face.c-1);
float x3 = v3.x;
float y3 = v3.y;
float z3 = v3.z;
glVertex3f(x3,y3,z3);
glEnd();
}
glPopMatrix();
glutSwapBuffers();
}

渲染结果

茶壶