加载 OBJ 文件时出现分段错误
Getting Segmentation fault while loading OBJ file
将这段代码添加到我的 OpenGL 程序后,我在 C++ 中使用 gcc 'Segmentation fault(core dumped)'
vector<GLfloat> vecTofloat(vector<glm::vec3> veca){
vector<GLfloat> fa;
for (int i = 0; i < veca.size(); i++){
glm::vec3 vec = veca[i];
fa.push_back(vec.x);
fa.push_back(vec.y);
fa.push_back(vec.z);
}
return fa;
}
vector<GLfloat> vecTofloat(vector<glm::vec2> veca){
vector<GLfloat> fa;
for (int i = 0; i < veca.size(); i++){
glm::vec2 vec = veca[i];
fa.push_back(vec.x);
fa.push_back(vec.y);
}
return fa;
}
MeshInstance loadOBJ(const char* path, const char* texPath, int tw, int th){
vector<glm::vec3> vert;
vector<glm::vec3> tvert;
vector<GLuint> ivert;
vector<glm::vec3> norm;
vector<glm::vec3> tnorm;
vector<GLuint> inorm;
vector<glm::vec2> tex;
vector<glm::vec2> ttex;
vector<GLuint> itex;
FILE* file = fopen(path, "r");
if (file == NULL){
LOG_ERROR("Unable to load OBJ mesh.");
exit(-1);
}
while(true){
char start[128];
int res = fscanf(file, "%s", start);
if (res == EOF){
break;
} else if (strncmp(start, "v", strlen("v")) == 0){
glm::vec3 vertex;
fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
tvert.push_back(vertex);
} else if (strncmp(start, "vt", strlen("vt")) == 0){
glm::vec2 tex;
fscanf(file, "%f %f\n", &tex.x, &tex.y);
ttex.push_back(tex);
} else if (strncmp(start, "vn", strlen("vn")) == 0){
glm::vec3 nrm;
fscanf(file, "%f %f %f\n", &nrm.x, &nrm.y, &nrm.z);
tnorm.push_back(nrm);
} else if (strncmp(start, "f", strlen("f")) == 0){
std::string vertex1, vertex2, vertex3;
unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n",&vertexIndex[0],&uvIndex[0],&normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2] );
ivert.push_back(vertexIndex[0]);
ivert.push_back(vertexIndex[1]);
ivert.push_back(vertexIndex[2]);
itex.push_back(uvIndex[0]);
itex.push_back(uvIndex[1]);
itex.push_back(uvIndex[2]);
inorm.push_back(normalIndex[0]);
inorm.push_back(normalIndex[1]);
inorm.push_back(normalIndex[2]);
}
}
for (int i = 0; i < ivert.size(); i+=3){
unsigned int vertex = ivert[i];
vert.push_back(tvert[vertex - 1]);
}
for (int i = 0; i < itex.size(); i+=3){
unsigned int vertex = itex[i];
tex.push_back(ttex[vertex - 1]);
}
for (int i = 0; i < inorm.size(); i+=3){
unsigned int vertex = inorm[i];
norm.push_back(tnorm[vertex - 1]);
}
return LoadToVAO(vecTofloat(vert), ivert, vecTofloat(tex), texPath, tw, th);
}
它所做的只是加载一个 obj 文件。我知道这是由于访问已经删除的内存,但我无法弄清楚程序中出现分段错误的位置。
编辑:
我用 gdb 调试了程序,现在我得到了这个:
Starting program: /home/saroj/workspace/Rachaita3D/Rachaita3D step
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff5120700 (LWP 11819)]
Thread 1 "Rachaita3D" received signal SIGSEGV, Segmentation fault.
0x0000555555565f66 in __gnu_cxx::new_allocator<glm::vec<2, float, (glm::qualifier)0> >::construct<glm::vec<2, float, (glm::qualifier)0>, glm::vec<2, float, (glm::qualifier)0> const&> (this=0x7fffffffdca0, __p=0x555555950a50, __args#0=...)
at /usr/include/c++/8/ext/new_allocator.h:136
136 { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
编辑 2:
在 tex.push_back(ttex[vertex - 1]);
处,p ttex[vertex - 1] returns
Cannot access memory at address 0x0
出于某种原因,我的加载器没有加载纹理坐标和法向量。
你对数据做错了。你通过 Wavefront OBJ specification 了吗?
您的代码需要从正在加载的实际 3D 模型中获得非常精确的结构,而 OBJ 格式允许面元素的非常灵活的组合。
OBJ 文件不仅可以支持三角形,还可以支持由 4 个或更多顶点构成的多边形(全部为平面)。你不应该在任何地方硬编码数字 3,但也许你可以 skip/ignore 'lines' 由 2 个顶点组成。
数据向量的大小可能与 3 不同。通常,顶点坐标是 2 个分量,而不是 3 个分量,但有时您会看到 3 个甚至 4 个分量。
每个面都指定其顶点的组件索引,f
命令中的索引以多个变体给出 - 作为单个数字,仅指定位置。作为一对由 /
斜杠分隔的索引,指定位置和纹理坐标。作为索引的三元组,指定 position/texture_coords/normal_vector。当模型具有没有纹理坐标的位置和法线数据时,您会看到一个三元组的纹理坐标完全缺失,例如 1//2
。您必须跟踪这些差异,因为某些数据会完全丢失,并且对其进行索引会触发段错误。
另一个功能是索引本身。它们可以是负数,指定的不是数据数组中的位置,而是组件向量累积数组中当前位置的偏移量。
因此您的代码至少有两个问题肯定会导致段错误,具体取决于正在加载的确切 3D 文件。
您不检查 f
命令中是否存在顶点分量索引(纹理坐标或法线),因此读取该索引将失败,给出垃圾或未初始化的零.在执行 ttex[vertex - 1]
时,您将从该零中减去 1,并且您正在读取数据数组之外的内容。
您不检查索引是否为负数。所以你会再次读取数据数组之外的内容。
现在的问题在于您解析 Wavefront OBJ 命令的方式。
第一次检查 strncmp(start, "v", strlen("v"))
不检查命令是否为 v
,它检查命令 是否以 "v" 开始 。
当代码遇到vn
或vt
时,第一次检查会在开头找到'v',走错分支。您用不相关的数据填充 tvert
数组,将 tnorm
和 ttex
留空,因此代码在尝试索引这些数据时崩溃。
要么将字符串与 strcmp()
进行比较,要么重新排序检查,因此 v
将最后测试。
将这段代码添加到我的 OpenGL 程序后,我在 C++ 中使用 gcc 'Segmentation fault(core dumped)'
vector<GLfloat> vecTofloat(vector<glm::vec3> veca){
vector<GLfloat> fa;
for (int i = 0; i < veca.size(); i++){
glm::vec3 vec = veca[i];
fa.push_back(vec.x);
fa.push_back(vec.y);
fa.push_back(vec.z);
}
return fa;
}
vector<GLfloat> vecTofloat(vector<glm::vec2> veca){
vector<GLfloat> fa;
for (int i = 0; i < veca.size(); i++){
glm::vec2 vec = veca[i];
fa.push_back(vec.x);
fa.push_back(vec.y);
}
return fa;
}
MeshInstance loadOBJ(const char* path, const char* texPath, int tw, int th){
vector<glm::vec3> vert;
vector<glm::vec3> tvert;
vector<GLuint> ivert;
vector<glm::vec3> norm;
vector<glm::vec3> tnorm;
vector<GLuint> inorm;
vector<glm::vec2> tex;
vector<glm::vec2> ttex;
vector<GLuint> itex;
FILE* file = fopen(path, "r");
if (file == NULL){
LOG_ERROR("Unable to load OBJ mesh.");
exit(-1);
}
while(true){
char start[128];
int res = fscanf(file, "%s", start);
if (res == EOF){
break;
} else if (strncmp(start, "v", strlen("v")) == 0){
glm::vec3 vertex;
fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
tvert.push_back(vertex);
} else if (strncmp(start, "vt", strlen("vt")) == 0){
glm::vec2 tex;
fscanf(file, "%f %f\n", &tex.x, &tex.y);
ttex.push_back(tex);
} else if (strncmp(start, "vn", strlen("vn")) == 0){
glm::vec3 nrm;
fscanf(file, "%f %f %f\n", &nrm.x, &nrm.y, &nrm.z);
tnorm.push_back(nrm);
} else if (strncmp(start, "f", strlen("f")) == 0){
std::string vertex1, vertex2, vertex3;
unsigned int vertexIndex[3], uvIndex[3], normalIndex[3];
fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d\n",&vertexIndex[0],&uvIndex[0],&normalIndex[0], &vertexIndex[1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2] );
ivert.push_back(vertexIndex[0]);
ivert.push_back(vertexIndex[1]);
ivert.push_back(vertexIndex[2]);
itex.push_back(uvIndex[0]);
itex.push_back(uvIndex[1]);
itex.push_back(uvIndex[2]);
inorm.push_back(normalIndex[0]);
inorm.push_back(normalIndex[1]);
inorm.push_back(normalIndex[2]);
}
}
for (int i = 0; i < ivert.size(); i+=3){
unsigned int vertex = ivert[i];
vert.push_back(tvert[vertex - 1]);
}
for (int i = 0; i < itex.size(); i+=3){
unsigned int vertex = itex[i];
tex.push_back(ttex[vertex - 1]);
}
for (int i = 0; i < inorm.size(); i+=3){
unsigned int vertex = inorm[i];
norm.push_back(tnorm[vertex - 1]);
}
return LoadToVAO(vecTofloat(vert), ivert, vecTofloat(tex), texPath, tw, th);
}
它所做的只是加载一个 obj 文件。我知道这是由于访问已经删除的内存,但我无法弄清楚程序中出现分段错误的位置。
编辑: 我用 gdb 调试了程序,现在我得到了这个:
Starting program: /home/saroj/workspace/Rachaita3D/Rachaita3D step
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff5120700 (LWP 11819)]
Thread 1 "Rachaita3D" received signal SIGSEGV, Segmentation fault.
0x0000555555565f66 in __gnu_cxx::new_allocator<glm::vec<2, float, (glm::qualifier)0> >::construct<glm::vec<2, float, (glm::qualifier)0>, glm::vec<2, float, (glm::qualifier)0> const&> (this=0x7fffffffdca0, __p=0x555555950a50, __args#0=...)
at /usr/include/c++/8/ext/new_allocator.h:136
136 { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
编辑 2:
在 tex.push_back(ttex[vertex - 1]);
处,p ttex[vertex - 1] returns
Cannot access memory at address 0x0
出于某种原因,我的加载器没有加载纹理坐标和法向量。
你对数据做错了。你通过 Wavefront OBJ specification 了吗?
您的代码需要从正在加载的实际 3D 模型中获得非常精确的结构,而 OBJ 格式允许面元素的非常灵活的组合。
OBJ 文件不仅可以支持三角形,还可以支持由 4 个或更多顶点构成的多边形(全部为平面)。你不应该在任何地方硬编码数字 3,但也许你可以 skip/ignore 'lines' 由 2 个顶点组成。
数据向量的大小可能与 3 不同。通常,顶点坐标是 2 个分量,而不是 3 个分量,但有时您会看到 3 个甚至 4 个分量。
每个面都指定其顶点的组件索引,f
命令中的索引以多个变体给出 - 作为单个数字,仅指定位置。作为一对由 /
斜杠分隔的索引,指定位置和纹理坐标。作为索引的三元组,指定 position/texture_coords/normal_vector。当模型具有没有纹理坐标的位置和法线数据时,您会看到一个三元组的纹理坐标完全缺失,例如 1//2
。您必须跟踪这些差异,因为某些数据会完全丢失,并且对其进行索引会触发段错误。
另一个功能是索引本身。它们可以是负数,指定的不是数据数组中的位置,而是组件向量累积数组中当前位置的偏移量。
因此您的代码至少有两个问题肯定会导致段错误,具体取决于正在加载的确切 3D 文件。
您不检查
f
命令中是否存在顶点分量索引(纹理坐标或法线),因此读取该索引将失败,给出垃圾或未初始化的零.在执行ttex[vertex - 1]
时,您将从该零中减去 1,并且您正在读取数据数组之外的内容。您不检查索引是否为负数。所以你会再次读取数据数组之外的内容。
现在的问题在于您解析 Wavefront OBJ 命令的方式。
第一次检查 strncmp(start, "v", strlen("v"))
不检查命令是否为 v
,它检查命令 是否以 "v" 开始 。
当代码遇到vn
或vt
时,第一次检查会在开头找到'v',走错分支。您用不相关的数据填充 tvert
数组,将 tnorm
和 ttex
留空,因此代码在尝试索引这些数据时崩溃。
要么将字符串与 strcmp()
进行比较,要么重新排序检查,因此 v
将最后测试。