这篇文章介绍一下用OpenGL来表现模型对象。比如用3DMAX等工具做好一个模型后,我们希望能够在自己的程序中使用它。一般使用这些工具的导出功能导出特定的文件,然后读取其中我们感兴趣的部分,再把这些感兴趣的数据在我们的程序中组织好就可以了。利用OpenGL来表现静态模型是很简单的。
这里为了简单起见,模型对象文件为txt文本,只包含了模型的顶点个数,面个数,顶点坐标,索引信息。当然在一些复杂的模型文件中,还包括法线,贴图坐标等等信息。重要的问题是,如何组织这些信息。
- 219483
- 435667
- 3.031209 1.875606 1.422974
- 3.030240 1.863352 1.404835
- 3.030240 1.863352 1.404835
- 3.021032 1.846390 1.404004
- 3.021032 1.846390 1.404004
- 3.050020 1.888645 1.389481
- 3.040140 1.883706 1.400404
- .
- .
- .
- 2869 2977 2973
- 2973 2974 2869
- 2979 2978 2975
- 2980 2979 2976
- 2976 2979 2975
- 2982 3104 3101
- 3096 2983 2982
- 3094 2984 3096
- 2984 2983 3096
- 3094 2972 2984
- 2979 2873 2978
- 2979 2980 2988
- .
- .
如上面的信息一样,第一行数据为该模型的顶点个数,第二行数据为该模型的三角形面个数。后面的数据分别人顶点坐标和每一个面的顶点索引信息。很自然的,首先可以想到要在程序中表现该模型,需要vertex的数据类型,于是可以定义这样的结构体,可以包含顶点的坐标和法线信息。
- struct Vertex
- {
- double P[3];
- double N[3];
- };
这里的顶点结构包含了顶点坐标P和法线N。然后就是面的数据类型:
- struct Face
- {
- int Idx[3];
- };
一个面由3个顶点构成,只要有了顶点的索引就可以了。最后就是模型的数据类型了。
- struct Model
- {
- int vNum;
- int fNum;
- Vertex *VertList;
- Face *FaceList;
- };
这个数据类型包含了模型顶点数vNum,面数fNum,指向顶点列表的指针VertList和指向面列表的指针FaceList。然后这样只要用Model来定义一个模型就可以了。Model MyModel;下面来看看如果从文件中读取信息。可以把读取文件的部分写成一个函数,比如叫LoadModel,函数的参数只需要提供文件名就可以了。
- void LoadModel(char *fname)
- {
- FILE *fp;
- fp = fopen(fname, "r");
- fscanf(fp, "%d", &MyModel.vNum);
- fscanf(fp, "%d", &MyModel.fNum);
- MyModel.VertList = new Vertex [MyModel.vNum];
- MyModel.FaceList = new Face [MyModel.fNum];
- int vNum = MyModel.vNum;
- int fNum = MyModel.fNum;
- for (int i = 0; i < vNum; ++i)
- {
- double x, y, z;
- fscanf(fp, "%lf %lf %lf", &x, &y, &z);
- MyModel.VertList[i].P[0] = x;
- MyModel.VertList[i].P[1] = y;
- MyModel.VertList[i].P[2] = z;
- MyModel.VertList[i].N[0] = 0.0;
- MyModel.VertList[i].N[1] = 0.0;
- MyModel.VertList[i].N[2] = 0.0;
- }
- for (int i = 0; i < fNum; ++i)
- {
- int idx0, idx1, idx2;
- fscanf(fp, "%d %d %d", &idx0, &idx1, &idx2);
- MyModel.FaceList[i].Idx[0] = idx0;
- MyModel.FaceList[i].Idx[1] = idx1;
- MyModel.FaceList[i].Idx[2] = idx2;
- double x0, y0, z0;
- double x1, y1, z1;
- double x2, y2, z2;
- x0 = MyModel.VertList[idx0].P[0];
- y0 = MyModel.VertList[idx0].P[1];
- z0 = MyModel.VertList[idx0].P[2];
- x1 = MyModel.VertList[idx1].P[0];
- y1 = MyModel.VertList[idx1].P[1];
- z1 = MyModel.VertList[idx1].P[2];
- x2 = MyModel.VertList[idx2].P[0];
- y2 = MyModel.VertList[idx2].P[1];
- z2 = MyModel.VertList[idx2].P[2];
- double ax, ay, az;
- double bx, by, bz;
- double nx, ny, nz;
- ax = x1 - x0;
- ay = y1 - y0;
- az = z1 - z0;
- bx = x2 - x0;
- by = y2 - y0;
- bz = z2 - z0;
- nx = ay * bz - az * by;
- ny = az * bx - ax * bz;
- nz = ax * by - ay * bx;
- MyModel.VertList[idx0].N[0] += nx;
- MyModel.VertList[idx0].N[1] += ny;
- MyModel.VertList[idx0].N[2] += nz;
- MyModel.VertList[idx1].N[0] += nx;
- MyModel.VertList[idx1].N[1] += ny;
- MyModel.VertList[idx1].N[2] += nz;
- MyModel.VertList[idx2].N[0] += nx;
- MyModel.VertList[idx2].N[1] += ny;
- MyModel.VertList[idx2].N[2] += nz;
- }
- for (int i = 0; i < vNum; ++i)
- {
- double nx, ny, nz;
- nx = MyModel.VertList[i].N[0];
- ny = MyModel.VertList[i].N[1];
- nz = MyModel.VertList[i].N[2];
- double norm = sqrt(nx * nx + ny * ny + nz * nz);
- MyModel.VertList[i].N[0] /= norm;
- MyModel.VertList[i].N[1] /= norm;
- MyModel.VertList[i].N[2] /= norm;
- }
- }
上面的代码比较长,但是内容很简单。首先打开文件,然后分别以整型的方式读取顶点个数和面个数,保存在结构体变量中。然后为顶点列表和面列表动态申请内存空间,在循环中分别读取每个数据。由于顶点包括法线信息,所以这里还进行了法线的计算。这里采用平均法线,即把一个顶点的多个法线加在一起,然后再进行单位化。由于一个顶点可能被用于多个面,所以该顶点的法线就是所有面的面法线的和。然后在程序初始化的时候,我们就可以调用这个函数来进行对模型的初始化。最后就是里利用OpenGL来渲染模型了。有了上面的所有信息,要渲染模型就很简单了。我们只用利用画三角形的方法就可以了。
- glBegin(GL_TRIANGLES);
- for (int i = 0; i < MyModel.fNum; ++i)
- {
- int idx0, idx1, idx2;
- idx0 = MyModel.FaceList[i].Idx[0];
- idx1 = MyModel.FaceList[i].Idx[1];
- idx2 = MyModel.FaceList[i].Idx[2];
- glNormal3dv(MyModel.VertList[idx0].N);
- glVertex3dv(MyModel.VertList[idx0].P);
- glNormal3dv(MyModel.VertList[idx1].N);
- glVertex3dv(MyModel.VertList[idx1].P);
- glNormal3dv(MyModel.VertList[idx2].N);
- glVertex3dv(MyModel.VertList[idx2].P);
- }
- glEnd();
可以看到,渲染部分很简单,就是在循环中依次访问面列表中每个面。并且给出每个面每个顶点的法线。