绘制 OBJ 模型时,OpenGL 无法正确剔除面

问题描述

我正在尝试从 OBJ 文件渲染茶壶模型。我正在使用固定函数渲染管道,我无法更改为可编程管道。我也想将一些基本的照明和材质应用到场景中,所以我的茶壶应用了绿色闪亮的材质。但是,当我围绕 Y 轴旋转茶壶时,我可以清楚地看到茶壶的背面。

Teapot

这是我迄今为止尝试过的:

  • 改变 OpenGL 剔除面(GL_ccw、GL_CW、GL_FRONT、GL_BACK)的方式,但没有产生正确的结果。

  • 改变 OpenGL 计算正面(GL_FRONT、GL_ccw、GL_BACK、GL_CW)的方式,但没有产生正确的结果。

  • 测试 OBJ 文件以确保它正确地对其顶点进行排序。当我将文件拖入 https://3dviewer.net/ 时,它会显示正确的不透明茶壶。

  • 改变灯光看看是否有任何作用。在某些情况下,改变照明并不能阻止茶壶被透视。

  • 禁用 GL_BLEND。这什么都没做

这是我目前启用的:

    glLightfv(GL_LIGHT0,GL_AMBIENT,light0Color);
    glLightfv(GL_LIGHT0,GL_DIFFUSE,light0DiffColor);
    glLightfv(GL_LIGHT0,GL_specular,light0SpecColor);
    glLightfv(GL_LIGHT0,GL_POSITION,position);
    gllightmodelfv(GL_LIGHT_MODEL_AMBIENT,ambientIntensity);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_norMALIZE);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_ccw);
    glFrontFace(GL_ccw);

以下是材料属性

    float amb[4] = {0.0215,0.1745,0.0215,1.0};
    float diff[4] = {0.07568,0.61424,0.07568,1.0};
    float spec[4] = {0.633,0.727811,0.633,1.0};
    float shininess = 0.6 * 128;
    glMaterialfv(GL_FRONT,amb);
    glMaterialfv(GL_FRONT,diff);
    glMaterialfv(GL_FRONT,spec);
    glMaterialf(GL_FRONT,GL_SHINInesS,shininess);

这是渲染代码

    glClearColor(0.0,0.0,1.0);
    glClearDepth(1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0,-150);
    glrotatef(r,1.0,0.0);
    glScalef(0.5,0.5,0.5);
    r += 0.5;
    m.draw(0,0);

我不确定这是否是问题的原因,但我在下面包含了模型加载代码,以防万一:

            while(std::getline(stream,line))
            {
                if (line[0] == 'v' && line[1] == 'n') // If we see a vertex normal in the OBJ file
                {
                    line = line.substr(3,line.size() - 3); // Removes the 'vn ' from the line
                    std::stringstream ss(line);
                    glm::vec3 normal;
                    ss >> normal.x >> normal.y >> normal.z;
                    tempnormalData.push_back(normal);
                }

                if (line[0] == 'v') // If we see a vertex on this line of the OBJ file
                {
                    line = line.substr(2,line.size() - 2); // Removes the 'v ' from the line
                    std::stringstream ss(line);
                    glm::vec3 position;
                    ss >> position.x >> position.y >> position.z;
                    tempVertData.push_back(position);
                }

                if (line[0] == 'f') // If we see a face in the OBJ file
                {
                    line = line.substr(2,line.size() - 2); // Removes the 'f ' from the line
                    std::stringstream ss(line);
                    glm::vec3 faceData;
                    ss >> faceData.x >> faceData.y >> faceData.z;
                    tempFaceData.push_back(faceData);
                }
            }

            if (tempVertData.size() != tempnormalData.size() && tempnormalData.size() > 0)
            {
                std::cout << "Not the same number of normals as vertices" << std::endl;
            }
            else
            {
                for (int i = 0; i < (int)tempVertData.size(); i++)
                {
                    Vertex v;
                    v.setPosition(tempVertData[i]);
                    v.setnormal(tempnormalData[i]);
                    vertices.push_back(v);
                }

                for (int i = 0; i < tempFaceData.size(); i++)
                {
                    Vertex v1 = vertices[tempFaceData[i].x - 1];
                    Vertex v2 = vertices[tempFaceData[i].y - 1];
                    Vertex v3 = vertices[tempFaceData[i].z - 1];
                    Face face(v1,v2,v3);
                    faces.push_back(face);
                }
            }
        }

最后,当我绘制人脸时,我只是遍历人脸列表并在人脸对象上调用 draw 函数。 face draw 函数只是包装了一个 glBegin(GL_TRIANGLES) 和一个 glEnd() 调用

    for (int i = 0; i < (int)faces.size(); i++)
    {
        auto& f = faces[i];
        f.draw(position);
    }

人脸绘制功能

glBegin(GL_TRIANGLES);
                glVertex3f(position.x + v1.getPosition().x,position.y + v1.getPosition().y,position.z + v1.getPosition().z);
                glnormal3f(v1.getnormal().x,v1.getnormal().y,v1.getnormal().z);

                glVertex3f(position.x + v2.getPosition().x,position.y + v2.getPosition().y,position.z + v2.getPosition().z);
                glnormal3f(v2.getnormal().x,v2.getnormal().y,v2.getnormal().z);

                glVertex3f(position.x + v3.getPosition().x,position.y + v3.getPosition().y,position.z + v3.getPosition().z);
                glnormal3f(v3.getnormal().x,v3.getnormal().y,v3.getnormal().z);
        glEnd();

我真的不想实现我自己的 Z-Buffer 剔除算法,我希望有一个非常简单的方法可以解决我刚刚遗漏的问题。

解决方案(感谢 Genpfault)

我没有从 OpenGL 请求深度缓冲区。我使用 Qt 作为我的窗口 API,所以我必须从我的格式对象中请求它,如下所示:
format.setDepthBufferSize(32);

这需要一个 32 位的深度缓冲区,从而解决了这个问题。

解决方法

为了使面部剔除工作,您需要:

  1. 定义缠绕规则

    glFrontFace(GL_CCW); // or GL_CW depends on your model and coordinate systems
    
  2. 设置要跳过的人脸

    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK); // or GL_FRONT depends on what you want to achieve
    

    正如您所看到的,这是您的代码中存在错误的地方,因为您使用错误的参数调用它很可能会导致新的 glError 条目。

  3. 在凹面网格的情况下,您还需要深度缓冲

    glEnable(GL_DEPTH_TEST);
    

    但是,在上下文创建期间,您的 OpenGL 上下文必须在其 pixelformat 中分配了深度缓冲区位。最安全的值是 16 位和 24 位,但是现在任何像样的 gfx 卡也可以处理 32 位。如果您需要更多,则需要使用 FBO。

  4. 具有一致多边形缠绕的网格

    wavefront obj 文件因缠绕不一致而臭名昭著,因此如果您看到某些三角形翻转了网格文件本身中最有可能的错误。

    这可以通过使用一些 3D 工具或通过 detecting wrong triangles 并反转它们的顶点和翻转法线来解决。

此外,您的渲染代码 glBegin/glEnd 的编写方式非常低效:

glVertex3f(position.x + v1.getPosition().x,position.y + v1.getPosition().y,position.z + v1.getPosition().z);
glNormal3f(v1.getNormal().x,v1.getNormal().y,v1.getNormal().z);

对于每个组件/操作数,您调用一些类成员函数,甚至进行算术运算……position 可以在实际的 glTranslate 矩阵中使用简单的 GL_MODELVIEW 完成,如果您有一些 3D 矢量类尝试将其组件作为指针访问,并使用 glVertex3fvglNormal3fv 代替,这样会快得多。