OpenGL读取Obj模型文件

21 篇文章 1 订阅

OpenGL读取Obj模型文件  

2012-09-07 19:16:01|  分类: 计算机图形学|举报|字号 订阅

下载LOFTER客户端

昨天,帮助别人写了一个程序,读取obj文件中的3D模型,就学习了下使用OpenGL如何读取这种文件。

Obj文件格式

想要顺利读取obj模型文件,先要了解这种文件的格式,OBJ文件格式是非常简单的。这种文件以纯文本的形式存储了模型的顶点、法线和纹理坐标和材质使用信息。OBJ文件的每一行,都有极其相似的格式。在OBJ文件中,每行的格式如下:

前缀 参数1 参数2 参数3 ...

其中,前缀标识了这一行所存储的信息类型。参数则是具体的数据。OBJ文件常见的的前缀有

  • v 表示本行指定一个顶点。 前缀后跟着3个单精度浮点数,分别表示该定点的X、Y、Z坐标值
  • vt 表示本行指定一个纹理坐标。此前缀后跟着两个单精度浮点数。分别表示此纹理坐标的U、V值
  • vn 表示本行指定一个法线向量。此前缀后跟着3个单精度浮点数,分别表示该法向量的X、Y、Z坐标值
  • f 表示本行指定一个表面(Face)。一个表面实际上就是一个三角形图元。此前缀行的参数格式后面将详细介绍。
  • usemtl 此前缀后只跟着一个参数。该参数指定了从此行之后到下一个以usemtl开头的行之间的所有表面所使用的材质名称。该材质可以在此OBJ文件所附属的MTL文件中找到具体信息。
  • mtllib 此前缀后只跟着一个参数。该参数指定了此OBJ文件所使用的材质库文件(*.mtl)的文件路径

现在,我们再来看一下OBJ文件的结构。在一个OBJ文件中,首先有一些以v、vt或vn前缀开头的行指定了所有的顶点、纹理坐标、法线的坐标。然后再由一些以f开头的行指定每一个三角形所对应的顶点、纹理坐标和法线的索引。在顶点、纹理坐标和法线的索引之间,使用符号“/”隔开的。一个f行可以以下面几种格式出现:

  • f  1  2  3 这样的行表示以第1、2、3号顶点组成一个三角形。
  • f  1/3  2/5  3/4 这样的行表示以第1、2、3号顶点组成一个三角形,其中第一个顶点的纹理坐标的索引值为3,第二个顶点的纹理坐标的索引值为5,第三个顶点的纹理坐标的索引值为4。
  • f  1/3/4  2/5/6  3/4/2 这样的行表示以第1、2、3号顶点组成一个三角形,其中第一个顶点的纹理坐标的索引值为3,其法线的索引值是4;第二个顶点的纹理坐标的索引值为5,其法线的索引值是6;第三个顶点的纹理坐标的索引值为6,其法线的索引值是2。
  • f  1//4  2//6  3//2这样的行表示以第1、2、3号顶点组成一个三角形,且忽略纹理坐标。其中第一个顶点的法线的索引值是4;第二个顶点的法线的索引值是6;第三个顶点的法线的索引值是2。

值得注意的是文件中的索引值是以1作为起点的,这一点与C语言中以0作为起点有很大的不同。在渲染的时候应注意将从文件中读取的坐标值减去1。

obj文件在OpenGL中的读取

我拿到的Obj文件,内容如下:

# Max2Obj Version 4.0 Mar 10th, 2001 # # object Line01 to come ... # v -9.574153 -2.220963 -2.000000 v -7.893424 -2.280989 -2.000000 ...省略若干相同格式的行 v -7.195892 -1.380599 -0.980160 v -9.580536 -1.320573 -1.967912 # 160 vertices vn -0.071382 -1.998675 0.014198 vn -0.035691 -0.999338 0.007099 ...同样省略若干相同格式的行 vn -0.825224 1.736366 -0.551397 vn 0.039418 1.999438 0.026341 # 160 vertex normals g Line01 s 1 f 1//1 12//12 2//2 f 1//1 11//11 12//12 s 4 f 2//2 13//13 3//3 f 2//2 12//12 13//13 ...同样的省略若干相同格式的行 s 4 f 160//160 1//1 151//151 f 160//160 10//10 1//1 # 320 faces g

前面带有'#'的行是注释行,这个文件中包含的前缀有:v,表示顶点;vn,表示法线;g,表示组,行 "g Line01" 和行 "g" 之前的所有行表示一个名为"Line01"的组;f,表示一个面;s,表示光滑组。

由于文件中只出现顶点和法线数据,每个面存储顶点和法线索引,所以我们要声明如下几个全局函数:

int v_num=0; //记录点的数量 
int vn_num=0;//记录法线的数量
 int f_num=0; //记录面的数量 
GLfloat **vArr; //存放点的二维数组
 GLfloat **vnArr;//存放法线的二维数组 
int **fvArr; //存放面顶点的二维数组
 int **fnArr; //存放面法线的二维数组


string s1; GLfloat f2,f3,f4;

为了给存放顶点法线等二维数组分配存储空间,需要知道顶点和法线等的数量,使用下面的函数计算点、法线、面的数量:

int readfile(string addrstr) //将文件内容读到数组中去 

vArr=new GLfloat*[v_num];

for (int i=0;i<v_num;i++) 

{  

vArr[i]=new GLfloat[3]; 

vnArr=new GLfloat*[vn_num];

for (i=0;i<vn_num;i++) 

{ vnArr[i]=new GLfloat[3]; } 

fvArr=new int*[f_num];

fnArr=new int*[f_num]; 

for (i=0;i<f_num;i++)

{  

fvArr[i]=new int[3]; 

fnArr[i]=new int[3];

ifstream infile(addrstr.c_str()); 

string sline;//每一行

int ii=0,jj=0,kk=0;  

while(getline(infile,sline)) 

{ if(sline[0]=='v') 

{ if(sline[1]=='n')//vn 

{ istringstream sin(sline);  

sin>>s1>>f2>>f3>>f4;  

vnArr[ii][0]=f2;  

vnArr[ii][1]=f3;  

vnArr[ii][2]=f4; ii++; 

}  

else//v 

{  

istringstream sin(sline);

sin>>s1>>f2>>f3>>f4;  

vArr[jj][0]=f2;  

vArr[jj][1]=f3;  

vArr[jj][2]=f4; jj++;  

if (sline[0]=='f') //读取面

{ istringstream in(sline);  

GLfloat a;  

in>>s1;//去掉前缀f  

int i,k;  

for(i=0;i<3;i++)  

{ in>>s1; cout<<s1<<endl; //取得顶点索引和法线索引  

a=0; for(k=0;s1[k]!='/';k++)  

{ a=a*10+(s1[k]-48); }  

fvArr[kk][i]=a; a=0;  

for(k=k+2;s1[k];k++)  

{ a=a*10+(s1[k]-48);;  

} fnArr[kk][i]=a;  

} kk++; 

} return 0; 


}

然后在绘制之前,初始化时,调用这两个函数读取模型即可:

getLineNum("wan.obj"); readfile("wan.obj");

相应的绘制代码:

for (int i=0;i<f_num;i++) 

{ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 

glBegin(GL_TRIANGLES); 

glNormal3f(vnArr[fnArr[i][0]-1][0], vnArr[fnArr[i][0]-1][1],  vnArr[fnArr[i][0]-1][2]);

glVertex3f(vArr[fvArr[i][0]-1][0], vArr[fvArr[i][0]-1][1],  vArr[fvArr[i][0]-1][2]);  

glNormal3f(vnArr[fnArr[i][1]-1][0], vnArr[fnArr[i][1]-1][1],  vnArr[fnArr[i][1]-1][2]); 

glVertex3f(vArr[fvArr[i][1]-1][0], vArr[fvArr[i][1]-1][1],  vArr[fvArr[i][1]-1][2]); 

glNormal3f(vnArr[fnArr[i][2]-1][0], vnArr[fnArr[i][2]-1][1],  vnArr[fnArr[i][2]-1][2]); 

glVertex3f(vArr[fvArr[i][2]-1][0], vArr[fvArr[i][2]-1][1],  vArr[fvArr[i][2]-1][2]);  

glEnd();

}

这样就完成了绘制,上面的代码仅仅针对我的wan.obj这个文件,对于想读取其他的obj文件,相应的分配一个存储空间,读取相应的数据,然后在绘制时使用这些数据就行了。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在Qt OpenGL读取OBJ模型文件,可以使用Qt自带的QOpenGLFunctions库。 首先,需要在项目文件中添加以下依赖: ``` QT += opengl ``` 然后,可以使用QOpenGLFunctions类来加载模型文件和绘制模型。 以下是一个简单的示例代码,可以读取和绘制一个OBJ模型文件: ```c++ #include <QOpenGLFunctions> #include <QOpenGLShaderProgram> #include <QOpenGLBuffer> #include <QVector3D> #include <QVector2D> #include <QFile> #include <QStringList> struct VertexData { QVector3D position; QVector2D texCoord; }; class ObjModel : protected QOpenGLFunctions { public: ObjModel(); virtual ~ObjModel(); void init(QString filename); void render(); private: QOpenGLShaderProgram m_program; QOpenGLBuffer m_vbo; int m_vertexCount; }; ObjModel::ObjModel() : m_vertexCount(0) { } ObjModel::~ObjModel() { m_vbo.destroy(); } void ObjModel::init(QString filename) { initializeOpenGLFunctions(); // Load OBJ file QFile file(filename); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; QVector<QVector3D> positions; QVector<QVector2D> texCoords; while (!file.atEnd()) { QByteArray line = file.readLine().trimmed(); QList<QByteArray> tokens = line.split(' '); if (tokens.isEmpty()) continue; if (tokens[0] == "v") { positions.append(QVector3D(tokens[1].toFloat(), tokens[2].toFloat(), tokens[3].toFloat())); } else if (tokens[0] == "vt") { texCoords.append(QVector2D(tokens[1].toFloat(), tokens[2].toFloat())); } else if (tokens[0] == "f") { for (int i = 1; i < tokens.size(); ++i) { QList<QByteArray> face = tokens[i].split('/'); VertexData data; data.position = positions[face[0].toInt() - 1]; data.texCoord = texCoords[face[1].toInt() - 1]; m_vertices.append(data); } } } m_vertexCount = m_vertices.size(); // Create VBO m_vbo.create(); m_vbo.bind(); m_vbo.allocate(m_vertices.constData(), m_vertexCount * sizeof(VertexData)); // Load shader program m_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/obj.vert"); m_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/obj.frag"); m_program.link(); } void ObjModel::render() { m_program.bind(); m_vbo.bind(); m_program.enableAttributeArray("position"); m_program.enableAttributeArray("texCoord"); m_program.setAttributeBuffer("position", GL_FLOAT, offsetof(VertexData, position), 3, sizeof(VertexData)); m_program.setAttributeBuffer("texCoord", GL_FLOAT, offsetof(VertexData, texCoord), 2, sizeof(VertexData)); glDrawArrays(GL_TRIANGLES, 0, m_vertexCount); m_vbo.release(); m_program.release(); } ``` 在上面的示例代码中,我们使用QFile类来读取OBJ文件,然后使用QOpenGLBuffer类创建一个VBO,并将OBJ文件中的顶点数据存储到VBO中。最后,使用QOpenGLShaderProgram类加载并绑定着色器程序,并使用glDrawArrays函数绘制模型。 注意,上面的代码仅仅是一个简单的示例,不足以处理所有的OBJ文件。在实际开发中,还需要对OBJ文件中的各种情况进行判断和处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值