从.obj文件中在OpenGL中绘制四边形。

3

我正在将一个由Blender创建的.obj文件解析到OpenGL的C++程序中,看起来已经接近成功了。它似乎可以正确地找到所需的顶点。我正在尝试创建一个可以移动的大写字母I方块。以下是我的解析代码:

bool loadObjectFile(const char * filepath, std::vector < glm::vec3 > & out_vertices, std::vector < glm::vec3 > & out_normals)
{
    std::vector< unsigned int > vertexIndices;
    std::vector< glm::vec3 > temp_vertices;
    std::vector< glm::vec3 > temp_normals;
    glm::vec3 tempFaceNormal;

    FILE * file = fopen(filepath, "r");
    if (file == NULL)
    {
        printf("Impossible to open the file !\n");
        return false;
    }

    while (1)
    {
        char lineHeader[128];
        // read the first word of the line
        int res = fscanf(file, "%s", lineHeader);
        if (res == EOF)
            break; // EOF = End Of File. Quit the loop.

        // else : parse lineHeader
        if (strcmp(lineHeader, "v") == 0)
        {
            glm::vec3 vertex;
            fscanf(file, "%f %f %f\n", &vertex.x, &vertex.y, &vertex.z);
            temp_vertices.push_back(vertex);
            glm::vec3 zeroface;
            zeroface.x = 0, zeroface.y = 0, zeroface.z = 0;
            temp_normals.push_back(zeroface);
        }
        else if (strcmp(lineHeader, "f") == 0)
        {
            unsigned int vertexIndex[3];
            int matches = fscanf(file, "%d %d %d\n", &vertexIndex[0], &vertexIndex[1], &vertexIndex[2]);

            if (matches != 3)
            {
                printf("File can't be read by our simple parser : ( Try exporting with other options\n");
                return false;
            }

            vertexIndices.push_back(vertexIndex[0]);
            vertexIndices.push_back(vertexIndex[1]);
            vertexIndices.push_back(vertexIndex[2]);

            tempFaceNormal = computeNormal(temp_vertices[vertexIndex[0] - 1], temp_vertices[vertexIndex[1] - 1], temp_vertices[vertexIndex[2] - 1]);

            temp_normals[vertexIndex[0] - 1] += tempFaceNormal;
            temp_normals[vertexIndex[1] - 1] += tempFaceNormal;
            temp_normals[vertexIndex[2] - 1] += tempFaceNormal;
        }
    }

    // For each vertex of each triangle
    for (unsigned int i = 0; i < vertexIndices.size(); i++)
    {
        unsigned int vertexIndex = vertexIndices[i];
        glm::vec3 vertex = temp_vertices[vertexIndex - 1];
        out_vertices.push_back(vertex);
    }

    //For each vertex normal
    for (unsigned int i = 0; i < temp_normals.size(); i++)
    {
        out_normals.push_back(glm::normalize(temp_normals[i]));
    }
}

glm::vec3 computeNormal(glm::vec3 const & a, glm::vec3 const & b, glm::vec3 const & c)
{
    //Returns the (not normalized) face normal of each triangle
    return glm::cross(c - a, b - a);
}

这是我用来绘制解析顶点的代码:
glBegin(GL_QUADS);
glColor3f(0.2, 0.2, 0);
for (int i = 0; i < vertices.size(); i++)
{
    glVertex3f(vertices[i].x, vertices[i].y, vertices[i].z);
    glTexCoord3f(vertices[i].x, vertices[i].y, vertices[i].z);
}
for (int i = 0; i < normals.size(); i++)
{
    glNormal3f(normals[i].x, normals[i].y, normals[i].z);
}
glEnd();

这将产生以下结果(截图):

https://drive.google.com/file/d/0B6qCowcn51DnQk1mT0hSUjZWc00/view?usp=sharing

https://drive.google.com/file/d/0B6qCowcn51DndTBJRFctV1lOQlk/view?usp=sharing

这里,顶点似乎是正确的,但面似乎被错误地绘制了。


它看起来你的解析代码假定 OBJ 文件包含三角形,但是你在绘制时使用了GL_QUADS。这段代码有很多问题,不确定这是否特定到足以适用于 StackOverflow 的格式。 - Reto Koradi
你能具体说明一下这段代码还有哪些问题吗?使用GL_QUADS和GL_QUAD_STRIP绘制似乎也会产生类似丑陋的结果。 - user3911542
自OpenGL 3.1版本起,glVertex3f、glBegin、glColor3f、glEnd等函数已被弃用。 - Adrian Maire
好的...那么你推荐使用哪些函数呢?我对OpenGL还不是很熟悉。 - user3911542
在现代OpenGL中,您将使用所谓的VBO(顶点缓冲对象)和VAO(顶点数组对象)来存储顶点、法线等,然后使用glDrawArrays或glDrawElements绘制网格。我建议您查看http://www.opengl-tutorial.org/上的教程。它们是一系列非常好的、现代的OpenGL教程。 在第7个教程中,它介绍了一个obj模型加载器的创建过程。 - K_Finlay
2个回答

5

这段代码存在一些问题。我不会对大多数问题进行详细说明,但至少想指出主要问题。

OBJ解析

你所做的主要部分可能很好,只要你自己生成OBJ文件并且控制它们的内容。如果你想能够解析更通用的OBJ文件,你需要更多的东西。例如:

  • "v"和"f"之外的其他记录类型。OBJ文件可以包含其他数据。其中一部分像法线("vn")和纹理坐标("vt")经常对OpenGL渲染至关重要。除此之外,还有许多其他可能或不可能有用的记录类型,具体取决于你想要什么。但至少你必须能够跳过你没有解析的记录。
  • 索引值可以是负数,使它们相对于当前位置。
  • 面可以有任意数量的顶点。
  • 面的格式有每个顶点最多3个索引(顶点/纹理坐标/法线),由斜杠分隔。

原始类型不匹配

这可能是最大的问题。你的解析代码每个面读取3个顶点,这意味着它期望所有的面都是三角形:

int matches = fscanf(file, "%d %d %d\n", &vertexIndex[0], &vertexIndex[1], &vertexIndex[2]);
if (matches != 3)
{
    printf("File can't be read by our simple parser : ( Try exporting with other options\n");
    return false;
}

然而,渲染代码将面渲染为四边形:

glBegin(GL_QUADS);

如果文件包含三角形,您需要将其作为渲染的原始类型使用:
glBegin(GL_TRIANGLES);

如果它包含四边形,您需要解析每个面的4个顶点索引,并相应地更新其余的解析代码。

法线数组

该代码包含重新排列顶点的逻辑,基于在面中顶点索引的顺序:

for (unsigned int i = 0; i < vertexIndices.size(); i++)
{
    unsigned int vertexIndex = vertexIndices[i];
    glm::vec3 vertex = temp_vertices[vertexIndex - 1];
    out_vertices.push_back(vertex);
}

但是它只是按照顶点的原始顺序复制法线:
for (unsigned int i = 0; i < temp_normals.size(); i++)
{
    out_normals.push_back(glm::normalize(temp_normals[i]));
}

相反,它需要像处理顶点一样重新排列法线。这可以作为顶点循环的一部分完成:

for (unsigned int i = 0; i < vertexIndices.size(); i++)
{
    unsigned int vertexIndex = vertexIndices[i];
    out_vertices.push_back(temp_vertices[vertexIndex - 1]);
    out_normals.push_back(glm::normalize(temp_normals[vertexIndex - 1]));
}

渲染时法线规范

在遍历顶点之后,渲染代码包含了一个独立的循环来处理法线。以这种方式指定法线并没有什么作用。需要在调用glVertex3fv()之前调用glNormal3fv()来指定该特定顶点的法线。

渲染循环不应该有两个独立的循环,而应该像这样:

for (int i = 0; i < vertices.size(); i++)
{
    glNormal3f(normals[i].x, normals[i].y, normals[i].z);
    glVertex3f(vertices[i].x, vertices[i].y, vertices[i].z);
}
glTexCoord3f() 的调用应该在 glVertex3f() 之前,但是您一开始就没有纹理坐标。

过时的OpenGL

您正在使用的渲染调用——glBegin()/glEnd()glVertex3f() 等,是遗留功能。这通常被称为即时模式,已被弃用,并且在现代版本的OpenGL(OpenGL 3.x/4.x核心配置文件,OpenGL ES 2.0/3.x)中不再可用。
对于现代渲染,首先要学习有关顶点缓冲区对象(VBO)和顶点数组对象(VAO)的知识,以指定您的顶点。然后,您可以使用 glDrawElements()glDrawArrays() 以单个调用绘制整个对象。

0

我最终弄清楚了该怎么做。因为我已经将.obj文件从Bender导出为一组四边形,所以我使用了GL_QUADS和GL_QUADSTRIP。由于某种原因,这两个产生了有点奇怪、丑陋的结果。我只是将.obj文件重新导入到Blender中,并将网格从四边形转换为三角形。然后,我将这些文件提供给了这段代码,一切都正常工作了。谢谢大家!


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