OpenGL读取Obj模型文件

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

Obj文件格式

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

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

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

§ v 表示本行指定一个顶点。 前缀后跟着3个单精度浮点数,分别表示该定点的XYZ坐标值

§ vt 表示本行指定一个纹理坐标。此前缀后跟着两个单精度浮点数。分别表示此纹理坐标的UV

§ vn 表示本行指定一个法线向量。此前缀后跟着3个单精度浮点数,分别表示该法向量的XYZ坐标值

§ f 表示本行指定一个表面(Face)。一个表面实际上就是一个三角形图元。此前缀行的参数格式后面将详细介绍。

§ usemtl 此前缀后只跟着一个参数。该参数指定了从此行之后到下一个以usemtl开头的行之间的所有表面所使用的材质名称。该材质可以在此OBJ文件所附属的MTL文件中找到具体信息。

§ mtllib 此前缀后只跟着一个参数。该参数指定了此OBJ文件所使用的材质库文件(*.mtl)的文件路径

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

§ f  1  3 这样的行表示以第123号顶点组成一个三角形。

§ f  1/3  2/5 3/4 这样的行表示以第123号顶点组成一个三角形,其中第一个顶点的纹理坐标的索引值为3,第二个顶点的纹理坐标的索引值为5,第三个顶点的纹理坐标的索引值为4

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

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

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

obj文件在OpenGL中的读取

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

# Max2Obj Version 4.0 Mar 10th, 2001

#

# object Line01 to come ...

#

-9.574153-2.220963 -2.000000

-7.893424-2.280989 -2.000000

...省略若干相同格式的行

-7.195892-1.380599 -0.980160

-9.580536-1.320573 -1.967912

# 160 vertices

 

vn  -0.071382-1.998675 0.014198

vn  -0.035691-0.999338 0.007099

...同样省略若干相同格式的行

vn  -0.8252241.736366 -0.551397

vn  0.0394181.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 (inti=0;i<v_num;i++)

{

  vArr[i]=newGLfloat[3];

}

vnArr=new GLfloat*[vn_num];

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

{

  vnArr[i]=newGLfloat[3];

}

fvArr=new int*[f_num];

fnArr=new int*[f_num];

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

{

  fvArr[i]=newint[3];

  fnArr[i]=newint[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') //读取面

{

  istringstreamin(sline);

  GLfloata;

 

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

  inti,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 (inti=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文件,相应的分配一个存储空间,读取相应的数据,然后在绘制时使用这些数据就行了。

******************************

以下是一个简单示例,演示如何使用pyOpenGL加载和渲染OBJ模型: ``` import pygame from OpenGL.GL import * from OpenGL.GLU import * from objloader import * # 初始化pygame窗口 pygame.init() viewport = (800, 600) pygame.display.set_mode(viewport, pygame.DOUBLEBUF | pygame.OPENGL) # 设置OpenGL参数 glClearColor(1, 1, 1, 1) glShadeModel(GL_SMOOTH) glEnable(GL_DEPTH_TEST) glEnable(GL_LIGHTING) glEnable(GL_LIGHT0) glLightfv(GL_LIGHT0, GL_POSITION, (0, 1, 1, 0)) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(45, (viewport[0] / viewport[1]), 0.1, 100.0) glMatrixMode(GL_MODELVIEW) # 加载OBJ模型 obj = OBJ("model.obj", swapyz=True) # 开始渲染 while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() # 清空缓冲区 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # 设置相机位置和方向 glLoadIdentity() gluLookAt(0, -8, 0, 0, 0, 0, 0, 0, 1) # 绘制OBJ模型 glPushMatrix() glTranslatef(0, 0, -5) glRotatef(0.0, 1.0, 0.0, 0.0) glRotatef(0.0, 0.0, 1.0, 0.0) glRotatef(0.0, 0.0, 0.0, 1.0) glCallList(obj.gl_list) glPopMatrix() # 刷新窗口 pygame.display.flip() pygame.time.wait(10) ``` 在上面的示例中,我们使用了一个名为“objloader”的模块来加载OBJ模型。该模块可以从以下链接中下载:https://github.com/pygame/pygame/blob/main/examples/glcube/objloader.py 在使用该模块之前,需要安装PyOpenGL和pygame库。可以使用以下命令来安装这些库: ``` pip install PyOpenGL PyOpenGL_accelerate pygame ``` 在渲染循环中,我们首先清空缓冲区,然后设置相机位置和方向。接着,我们使用glPushMatrix()和glPopMatrix()来保存和恢复OpenGL矩阵堆栈状态。最后,我们通过调用glCallList()函数来绘制OBJ模型
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值