使用C++解析Wavefront .obj文件

7
尝试解析波前.obj文件时,我想到了两种方法:
1. 创建一个大小等于顶点数的二维数组。当面使用一个顶点时,从数组中获取其坐标。
2. 获取顶点列表的起始位置,然后当面使用一个顶点时,扫描行直到找到该顶点。
在我看来,选项 1 将占用大量内存,但速度更快。由于选项 2 涉及大量的文件读取 (并且因为大多数对象中的顶点数变得非常大),所以它会更慢,但内存占用较少。
问题是:在内存和速度之间进行权衡,哪个选项更适合普通计算机?还有其他替代方法吗?
我打算使用 OpenGL 和 GLFW 来渲染这个对象。

虽然它只处理相当有限的子集,但你可能想看一下我在之前的回答中张贴的一些代码以获取灵感。 - Jerry Coffin
5个回答

6

在我看来,选项1将非常消耗内存,但速度更快。

无论如何,您必须将这些顶点加载到内存中。但是不需要使用2D数组,这会导致两个指针间接引用,从而造成主要性能损失。只需使用一个简单的std::vector<Vertex>作为您的数据,向量索引是伴随面列表的索引。

由于评论而进行编辑

class Vertex
{
    union { struct { float x, y, z }; float pos[3] };
    union { struct { float nx, ny, nz }; float normal[3] };
    union { struct { float s, t }; float pos[2] };
    Vertex &operator=();
}

std::vector<Vertex>;

但是你要如何存储x、y和z坐标呢? - viraj

4
一般情况下,您需要将顶点列表读入数组中。解析ASCII文本非常缓慢;只需在加载文件时执行一次,然后将所有内容存储在内存中的数组中。
三角形/面也是一样。每个三角形通常由三个顶点索引列表组成。这也应该存储在一个数组中。
您可能会发现VTK开源库中的OBJ阅读器很有用:http://www.vtk.org/doc/nightly/html/classvtkOBJReader.html。我们使用它并且没有写自己的理由...直接使用VTK,或者您可能会发现研究源代码对于自己的阅读器的启示很好。
在我看来,OBJ文件的主要缺点之一是使用ASCII。 3D ASCII文件(无论是STL,PLY,OBJ等)由于字符串解析而非常缓慢。如果性能是问题,则始终应使用二进制格式文件:良好二进制格式的加载时间即时。

3
“一个好的二进制格式加载时间是瞬间完成的。”你是在哪里买那些零寻道、无限带宽的硬盘驱动器?我也想要! :) - genpfault
1
也许我应该表达为“快上几个数量级”。二进制文件可以像磁盘驱动器将文件内容复制到RAM一样快速读取。我们通常使用浮点数矩阵,可以存储为ASCII或二进制文件。对于相同的值/矩阵大小,二进制文件的加载/保存时间不到一秒,而ASCII文件需要半分钟。(我们使用C++文件流类来转换为/从ASCII)。 ASCII格式仅用于调试... - James Johnston
不仅仅是我们,您可以尝试在MeshLab中打开/保存3D文件,并比较ASCII和二进制的性能。两者之间的差异明显不同。 - James Johnston
+1 针对 VTK 参考资料。他们的 OBJ 加载方法比我之前使用的方法简单 10 倍。 - viraj

2
只需将它们加载到数组中。内存不应该是一个问题。您的系统(通常)比您的GPU拥有更多的内存。如果您遇到内存问题,那么您可能正在加载一个过于详细的模型。(我半推测您将会在OpenGL中制作游戏。如果您有特定需要这样大模型文件的情况,您仍然需要找到一种方法来加载适当的块。)

0

这是一个相当不错的原型解决方案,运行生成OpenGL或您喜欢的渲染API中使用的数组的脚本。 obj2opengl.pl是一个perl脚本,您需要安装perl,可以在这里找到。GitHub链接在这里

在运行perl脚本时,您可能会在第154行遇到关于if(defined(@center))的运行时错误。请将其替换为if(@center)

从示例中,一旦生成了带有数据的头文件,您可以按如下方式使用它:

/*
created with obj2opengl.pl

source file    : ./banana.obj
vertices       : 4032
faces          : 8056
normals        : 4032
texture coords : 4420


// include generated arrays
#import "./banana.h"

// set input data to arrays
glVertexPointer(3, GL_FLOAT, 0, bananaVerts);
glNormalPointer(GL_FLOAT, 0, bananaNormals);
glTexCoordPointer(2, GL_FLOAT, 0, bananaTexCoords);

// draw data
glDrawArrays(GL_TRIANGLES, 0, bananaNumVerts);
*/

0

你不需要一个二维数组。你的模型应该是三角化的,然后你可以使用glut的obj加载器简单地加载obj文件。只需将点、面和法线存储在3个单独的数组/缓冲区中即可。这里有一个示例here,但如果你想快速完成,你应该选择二进制格式。


.obj文件不是由我生成的,它包含了四边形面。另外,这个二维数组之所以存在,是因为其中一个维度表示顶点编号,另一个维度存储了x、y和z坐标。 - viraj
@viraj:顶点号应该被用作数组的索引。这就是它的预定用途。 - datenwolf
同时,在从ASCII文件中读取浮点数时,我不能使用in >> number的方式吗?其中in是一个ifstream对象,number是一个浮点数? - viraj
读取四边形时,只需将其转换为2个三角形(即添加6个索引而不是4个;注意面向)。无论如何,您迟早都必须这样做。 - Calvin1602

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接