阅读《计算机图形学编程(使用OpenGL和C++)》11 - 加载外部obj模型

复杂的3D模型可以借助建模工具生成,这种工具能够在3D空间中构建任意形状并自动生成顶点、纹理坐标、顶点法向量等。模型生成后可导出成obj文件格式,这种格式有很多,OBJ文件很简单,我们可以相对容易地开发一个基本的导入器。在OBJ文件中,通过文本行的形式指定顶点几何数据、纹理坐标、法向量和其他信息。它有一些限制——例如,OBJ文件无法指定模型动画。

OBJ文件中的行,以字符标记开头,表示该行上的数据类型。一些常见的标签包括:

●v-几何(顶点位置)数据;

●vt-纹理坐标;

●vn-顶点法向量;

●f-面(通常是三角形中的顶点,每个元素的值分别是顶点列表、纹理坐标和法向量的索引。)。

obj格式文件导入器代码:

注意这个导入器非常有局限性,仅支持由单个三角形网格组成的OBJ模型(OBJ格式支持复合模型,但我们的简单导入器不支持),仅支持包含所有3个面属性字段的模型,它假设每行上的元素只用一个空格分隔。

ImportedModel.h,ImportedModel.cpp

1 #pragma once
  2 #include <vector>
  3 #include <glm\glm.hpp>
  4 
  5 class ModelImporter
  6 {
  7 public:
  8     ModelImporter();
  9     ~ModelImporter();
 10     void parseOBJ(const char *filePath);
 11     int getNumVertices();
 12     std::vector<float> getVertices();
 13     std::vector<float> getTextureCoordinates();
 14     std::vector<float> getNormals();
 15 private:
 16     // 从OBJ文件读取的数值
 17     std::vector<float> vertVals;
 18     std::vector<float> stVals;
 19     std::vector<float> normVals;
 20     // 保存为顶点属性以供后续使用的数值
 21     std::vector<float> triangleVerts;
 22     std::vector<float> textureCoords;
 23     std::vector<float> normals;
 24 };
 25 
 26 class ImportedModel
 27 {
 28 public:
 29     ImportedModel(const char *filePath);
 30     ~ImportedModel();
 31     int getNumVertices();
 32     std::vector<glm::vec3> getVertices();
 33     std::vector<glm::vec2> getTextureCoords();
 34     std::vector<glm::vec3> getNormals();
 35 
 36 private:
 37     int numVertices;
 38     std::vector<glm::vec3> vertices;
 39     std::vector<glm::vec2> texCoords;
 40     std::vector<glm::vec3> normalVecs;
 41     ModelImporter a;
 42 };
 43 
 44 #include "ImportedModel.h"
 45 #include <fstream>
 46 #include <sstream>
 47 using namespace std;
 48 
 49 ModelImporter::ModelImporter() {}
 50 
 51 ModelImporter::~ModelImporter(){}
 52 
 53 void ModelImporter::parseOBJ(const char *filePath)
 54 {
 55     float x, y, z;
 56     string content;
 57     ifstream fileStream(filePath, ios::in);
 58     string line = "";
 59     while (!fileStream.eof())
 60     {
 61         getline(fileStream, line);
 62         if (line.compare(0, 2, "v ") == 0) // 顶点位置("v"的情况)
 63         {
 64             stringstream ss(line.erase(0, 1));
 65             ss >> x; // 提取顶点位置数值
 66             ss >> y;
 67             ss >> z;
 68             vertVals.push_back(x);
 69             vertVals.push_back(y);
 70             vertVals.push_back(z);
 71         }
 72 
 73         if (line.compare(0, 2, "vt") == 0) // 纹理坐标("vt"的情况)
 74         {
 75             stringstream ss(line.erase(0, 2));
 76             ss >> x; // 提取纹理坐标数值
 77             ss >> y;
 78             stVals.push_back(x);
 79             stVals.push_back(y);
 80         }
 81 
 82         if (line.compare(0, 2, "vn") == 0) // 顶点法向量("vn"的情况)
 83         {
 84             stringstream ss(line.erase(0, 2));
 85             ss >> x; // 提取法向量数值
 86             ss >> y;
 87             ss >> z;
 88             normVals.push_back(x);
 89             normVals.push_back(y);
 90             normVals.push_back(z);
 91         }
 92 
 93         if (line.compare(0, 2, "f ") == 0) // 三角形面("f"的情况)
 94         {
 95             string oneCorner, v, t, n;
 96             stringstream ss(line.erase(0, 2));
 97             for (int i = 0; i < 3; i++)
 98             {
 99                 getline(ss, oneCorner, ' '); // 提取三角形面引用
100                 stringstream oneCornerSS(oneCorner);
101                 getline(oneCornerSS, v, '/');
102                 getline(oneCornerSS, t, '/');
103                 getline(oneCornerSS, n, '/');
104                 int vertRef = (stoi(v) - 1) * 3; // "stoi"将字符串转化为整型
105                 int tcRef = (stoi(t) - 1) * 2;
106                 int normRef = (stoi(n) - 1) * 3;
107                 triangleVerts.push_back(vertVals[vertRef]); // 构建顶点向量
108                 triangleVerts.push_back(vertVals[vertRef + 1]);
109                 triangleVerts.push_back(vertVals[vertRef + 2]);
110                 textureCoords.push_back(stVals[tcRef]); // 构建纹理坐标向量
111                 textureCoords.push_back(stVals[tcRef + 1]);
112                 normals.push_back(normVals[normRef]); // 法向量的向量
113                 normals.push_back(normVals[normRef + 1]);
114                 normals.push_back(normVals[normRef + 2]);
115             }
116         }
117     }
118 }
119 
120 int ModelImporter::getNumVertices()
121 {
122     return (triangleVerts.size() / 3);
123 }
124 
125 vector<float> ModelImporter::getVertices()
126 {
127     return triangleVerts;
128 }
129 
130 vector<float> ModelImporter::getTextureCoordinates()
131 {
132     return textureCoords;
133 }
134 
135 vector<float> ModelImporter::getNormals()
136 {
137     return normals;
138 }
139 
140 ImportedModel::ImportedModel(const char *filePath)
141 {
142     ModelImporter modelImporter = ModelImporter();
143     modelImporter.parseOBJ(filePath); // 使用modelImporter获取顶点信息
144     numVertices = modelImporter.getNumVertices();
145     vector<float> verts = modelImporter.getVertices();
146     vector<float> tcs = modelImporter.getTextureCoordinates();
147     vector<float> normals = modelImporter.getNormals();
148 
149     for (int i = 0; i < numVertices; i++)
150     {
151         vertices.push_back(glm::vec3(verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2]));
152         texCoords.push_back(glm::vec2(tcs[i * 2], tcs[i * 2 + 1]));
153         normalVecs.push_back(glm::vec3(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]));
154     }
155 }
156 
157 ImportedModel::~ImportedModel()
158 {
159 }
160 
161 int ImportedModel::getNumVertices()
162 {
163     return numVertices;
164 }
165 
166 vector<glm::vec3> ImportedModel::getVertices()
167 {
168     return vertices;
169 }
170 
171 vector<glm::vec2> ImportedModel::getTextureCoords()
172 {
173     return texCoords;
174 }
175 
176 vector<glm::vec3> ImportedModel::getNormals()
177 {
178     return normalVecs;
179 }

我用Blender生成一个立方体模型的OBJ文件,生成的OBJ文件中 "f" 面是四个点构成正方形,不是三角形,我们简单的导入器只读取了四个点中前三个点,形成的每个面都是三角形不是正方形。

为此我加了一个面,是第一个“f” 的另一个三角形,可以看到有一个面是完整的正方形。其他面不想加了,都同理。

 1 # Blender v3.2.1 OBJ File: ''
 2 # www.blender.org
 3 mtllib untitled.mtl
 4 o Cube
 5 v 2.982061 2.981781 -3.035211
 6 v 2.910542 0.983304 -3.066416
 7 v 3.085501 2.946897 -1.038193
 8 v 3.013981 0.948420 -1.069397
 9 v 0.986019 3.051580 -2.930603
10 v 0.914499 1.053103 -2.961807
11 v 1.089458 3.016696 -0.933584
12 v 1.017939 1.018219 -0.964789
13 vt 0.625000 0.500000
14 vt 0.875000 0.500000
15 vt 0.875000 0.750000
16 vt 0.625000 0.750000
17 vt 0.375000 0.750000
18 vt 0.625000 1.000000
19 vt 0.375000 1.000000
20 vt 0.375000 0.000000
21 vt 0.625000 0.000000
22 vt 0.625000 0.250000
23 vt 0.375000 0.250000
24 vt 0.125000 0.500000
25 vt 0.375000 0.500000
26 vt 0.125000 0.750000
27 vn 0.0358 0.9992 0.0156
28 vn 0.0517 -0.0174 0.9985
29 vn -0.9980 0.0349 0.0523
30 vn -0.0358 -0.9992 -0.0156
31 vn 0.9980 -0.0349 -0.0523
32 vn -0.0517 0.0174 -0.9985
33 usemtl Material
34 s off
35 f 1/1/1 5/2/1 7/3/1 3/4/1
36 f 4/5/2 3/4/2 7/6/2 8/7/2
37 f 8/8/3 7/9/3 5/10/3 6/11/3
38 f 6/12/4 2/13/4 4/5/4 8/14/4
39 f 2/13/5 1/1/5 3/4/5 4/5/5
40 f 6/11/6 5/10/6 1/1/6 2/13/6
41 f 1/1/1 7/3/1 3/4/1

main.cpp

 1 ...
 2 #include "ImportedModel.h"
 3 ...
 4 ImportedModel myModel("untitled.obj");
 5 ...
 6 void setupVertices(void)
 7 {    
 8     //std::vector<int> ind = myTorus.getIndices(); //索引
 9     std::vector<glm::vec3> vert = myModel.getVertices(); //顶点
10     std::vector<glm::vec2> tex = myModel.getTextureCoords(); //纹理
11     std::vector<glm::vec3> norm = myModel.getNormals(); //法向量
12     int numObjVertices = myModel.getNumVertices();
13 
14     std::vector<float> pvalues; //顶点位置
15     vector<float> tvalues;        //纹理坐标
16     vector<float> nvalues;        //法向量
17 
18     for (int i = 0; i < numObjVertices; i++)
19     {
20         pvalues.push_back((vert[i]).x);
21         pvalues.push_back((vert[i]).y);
22         pvalues.push_back((vert[i]).z);
23         tvalues.push_back((tex[i]).s);
24         tvalues.push_back((tex[i]).t);
25         nvalues.push_back((norm[i]).x);
26         nvalues.push_back((norm[i]).y);
27         nvalues.push_back((norm[i]).z);
28     }
29 
30     glGenVertexArrays(1, vao); // 创建一个vao,并返回它的整数型ID存进数组vao中
31     glBindVertexArray(vao[0]); // 激活vao
32     glGenBuffers(numVBOs, vbo);// 创建3个vbo,并返回它们的整数型ID存进数组vbo中
33 
34     // 把顶点放入缓冲区 #0
35     glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); 
36     glBufferData(GL_ARRAY_BUFFER, pvalues.size()*4, &pvalues[0], GL_STATIC_DRAW); 
37     // 把纹理坐标放入缓冲区 #1
38     glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); 
39     glBufferData(GL_ARRAY_BUFFER, tvalues.size() * 4, &tvalues[0], GL_STATIC_DRAW); 
40     // 把法向量放入缓冲区 #2
41     glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
42     glBufferData(GL_ARRAY_BUFFER, nvalues.size() * 4, &nvalues[0], GL_STATIC_DRAW);
43     // 把索引放入缓冲区 #3
44     //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
45     //glBufferData(GL_ELEMENT_ARRAY_BUFFER, ind.size() * 4, &ind[0], GL_STATIC_DRAW);
46 }
47 ...
48 void display(GLFWwindow* window, double currentTime)
49 {
50     ...
51     glDrawArrays(GL_TRIANGLES, 0, myModel.getNumVertices());
52 }

效果如下:

猜你喜欢

转载自blog.csdn.net/ttod/article/details/135346536