【问题标题】:OpenGL is not culling faces properly when drawing OBJ model绘制 OBJ 模型时,OpenGL 无法正确剔除面
【发布时间】:2021-07-17 05:03:04
【问题描述】:

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

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

  • 改变 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, 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);
                }
            }
        }

最后,当我绘制面时,我只是循环遍历面列表并在面对象上调用绘图函数。面部绘制函数只是包装了一个 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 位的深度缓冲区,从而解决了这个问题。

【问题讨论】:

  • minimal reproducible example 中编辑。就我们所知,在 OpenGL 上下文创建期间没有请求深度缓冲区。随意使用this 作为基础。
  • 缺乏深度缓冲区是我的直接猜测 - 很明显,面部正在被剔除,根据喷口底部,他们面对的方式,并且提问者表示问题只有在旋转后才会变得明显(即从适当的角度)。
  • 感谢两位的帮助!你是对的,我需要请求一个深度缓冲区。我使用 Qt 作为我的窗口 API,所以我需要从格式请求它。我选择请求一个 32 位深度的缓冲区。感谢您的帮助,帖子已被编辑以反映此解决方案。

标签: c++ opengl opengl-compat wavefront culling


【解决方案1】:

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

  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 代替,这样会快得多。

【讨论】:

    猜你喜欢
    • 2021-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-19
    • 2012-09-13
    • 2016-12-24
    • 1970-01-01
    • 2012-05-22
    相关资源
    最近更新 更多