问题描述
我正在尝试从 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,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);
解决方法
为了使面部剔除工作,您需要:
-
定义缠绕规则
glFrontFace(GL_CCW); // or GL_CW depends on your model and coordinate systems
-
设置要跳过的人脸
glEnable(GL_CULL_FACE); glCullFace(GL_BACK); // or GL_FRONT depends on what you want to achieve
正如您所看到的,这是您的代码中存在错误的地方,因为您使用错误的参数调用它很可能会导致新的
glError
条目。 -
在凹面网格的情况下,您还需要深度缓冲
glEnable(GL_DEPTH_TEST);
但是,在上下文创建期间,您的 OpenGL 上下文必须在其
pixelformat
中分配了深度缓冲区位。最安全的值是 16 位和 24 位,但是现在任何像样的 gfx 卡也可以处理 32 位。如果您需要更多,则需要使用 FBO。 -
具有一致多边形缠绕的网格
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 矢量类尝试将其组件作为指针访问,并使用 glVertex3fv
和 glNormal3fv
代替,这样会快得多。