OpenGL讀取Obj模型文件


昨天,幫助別人寫了一個程序,讀取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文件,相應的分配一個存儲空間,讀取相應的數據,然后在繪制時使用這些數據就行了。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM