绘制 OBJ 模型时,OpenGL 未正确剔除面孔

OpenGL is not culling faces properly when drawing OBJ model

我正在尝试从 OBJ 文件渲染茶壶模型。我正在使用固定功能渲染管线,无法更改为可编程管线。我还想对场景应用一些基本照明和 materials,所以我的茶壶应用了闪亮的绿色 material。但是,当我将茶壶绕着 Y-Axis 旋转时,我可以清楚地看到茶壶的背面。

这是我到目前为止尝试过的方法:

这是我目前启用的:

    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);

以下是 material 属性:

    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, GL_AMBIENT, amb);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, diff);
    glMaterialfv(GL_FRONT, GL_SPECULAR, spec);
    glMaterialf(GL_FRONT, GL_SHININESS, shininess);

渲染代码如下:

    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClearDepth(1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0, 0, -150);
    glRotatef(r, 0.0, 1.0, 0.0);
    glScalef(0.5, 0.5, 0.5);
    r += 0.5;
    m.draw(0, 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);
                }
            }
        }

最后,当我绘制面孔时,我只是循环遍历面孔列表并在面上调用绘制函数 object。面部绘制函数只是包装了一个 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,所以我不得不从我的格式 object 中请求它,如下所示:
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 工具或 并反转它们的顶点和翻转法线来解决。

另外你的渲染代码 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);

对于每个 component/operand 你调用一些 class 成员函数,甚至进行算术运算...... position 可以用简单的 glTranslate 在实际 GL_MODELVIEW 矩阵,如果你有一些 3D 矢量 class 尝试访问它的组件作为指针并使用 glVertex3fvglNormal3fv 而不是这样会快得多。