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

  • 相关阅读:
    JSON.parse解决Unexpected token ' in JSON at position 1报错
    angularjs $scope与this的区别,controller as vm有何含义?
    angularjs link compile与controller的区别详解,了解angular生命周期
    理解Promise.all,Promise.all与Promise.race的区别,如何让Promise.all在rejected失败后依然返回resolved成功结果
    angularjs 一篇文章看懂自定义指令directive
    js 记录几个因惯性思维引发的代码BUG,开发思维方式的自我反省
    js forEach参数详解,forEach与for循环区别,forEach中如何删除数组元素
    angularjs ng-if妙用,ng-if解决父子组件异步传值
    JS 从内存空间谈到垃圾回收机制
    Markdown数学公式语法
  • 原文地址:https://www.cnblogs.com/Anzhongliu/p/6092048.html
Copyright © 2020-2023  润新知