OpenGL深度缓冲区表现不如预期

时间:2014-05-27 19:08:31

标签: c++ opengl glsl depth-buffer depth-testing

我一直致力于教育追求的引擎的开端,并且我遇到了一个我认为理解的OpenGL概念,但是我无法解释我一直在观察的行为。问题在于深度缓冲区。另外,了解我已经解决了这个问题,在我的帖子结束时,我将解释解决问题的原因,但是我不明白为什么通过我的解决方案修复了问题。 首先我初始化GLUT& GLEW:

//Initialize openGL
glutInit(&argc, argv);
//Set display mode and window attributes
glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGB);

//Size and position attributes can be found in constants.h
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
glutInitWindowPosition(WINDOW_XPOS, WINDOW_YPOS);
//Create window
glutCreateWindow("Gallagher");

// Initialize GLEW
glewExperimental = true;
glewInit();

//Initialize Graphics program
Initialize();

然后我初始化我的程序(为了可读性和缺乏相关性而遗漏片段):

//Remove cursor
glutSetCursor(GLUT_CURSOR_NONE);

//Enable depth buffering
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LESS);
glDepthRange(0.0f, 1.0f)

//Set back color
glClearColor(0.0,0.0,0.0,1.0);

//Set scale and dimensions of orthographic viewport
//These values can be found in constants.h
//Program uses a right handed coordinate system.
glOrtho(X_LEFT, X_RIGHT, Y_DOWN, Y_UP, Z_NEAR, Z_FAR);

除此之外的任何事情只包括初始化各种引擎组件,加载.obj文件,初始化ModularGameObject类的实例,将网格附加到它们,没有任何触及任何相关的过剩/暴露。 但是,在我继续之前,指定以下值可能很重要:

X_LEFT = -1000;
X_RIGHT = 1000;
Y_DOWN = -1000;
Y_UP = 1000;
Z_NEAR = -0.1;
Z_FAR = -1000;

这导致我的视​​口遵循右手坐标系。 似乎涉及问题的最后一段代码是我的顶点着色器:

#version 330 core
//Position of vertices in attribute 0
layout(location = 0) in vec4 _vertexPosition;
//Vertex Normals in attribute 1
layout(location = 1) in vec4 _vertexNormal;

//Model transformations
//Uniform location of model transformation matrix
uniform mat4 _modelTransformation;

//Uniform location of camera transformations
//Camera transformation matrix
uniform mat4 _cameraTransformation;
//Camera perspective matrix
uniform mat4 _cameraPerspective;

//Uniform location of inverse screen dimensions
//This is used because GLSL normalizes viewport from -1 to 1
//So any vector representing a position in screen space must be multiplied by this vector before display
uniform vec4 _inverseScreenDimensions;

//Output variables
//Indicates whether a vertex is valid or not, non valid vertices will not be drawn.
flat out int _valid;        // 0 = valid vertex
//Normal to be sent to fragment shader
smooth out vec4 _normal;

void main()
{
    //Initiate transformation pipeline

    //Transform to world space
    vec4 vertexInWorldSpace = vec4(_modelTransformation *_vertexPosition);

    //Transform to camera space
    vec4 vertexInCameraSpace = vec4(_cameraTransformation * vertexInWorldSpace);

    //Project to screen space
    vec4 vertexInScreenSpace = vec4(_cameraPerspective * vertexInCameraSpace);

    //Transform to device coordinates and store
    vec4 vertexInDeviceSpace = vec4(_inverseScreenDimensions * vertexInScreenSpace);
    //Store transformed vertex
    gl_Position = vertexInScreenSpace;

}

此代码导致所有转换和正常计算(未包含)正确完成,但是我的模型的每个面都经常争先恐后地超越所有其他面。唯一没有问题的是站在第一个模型的内部,然后没有任何闪烁,我可以像我应该的那样查看Suzanne头部的内部。

经过几周的尝试,我可能会想到我终于找到了一个解决方案,其中涉及更改/添加仅仅两行代码。首先,我在顶点着色器中将此行添加到主函数的末尾:

gl_Position.z = 0.0001+vertexInScreenSpace.z;

添加这一行代码会导致z-fighting的每一点消失,除非现在深度缓冲区完全向后,更远的顶点被可靠地绘制在前面的顶点上。这是我的第一个问题,为什么这行代码会导致更可靠的行为?

既然我有可靠的行为而且没有更多的深度对抗,那就是扭转绘制顺序的问题,所以我将我的调用改为glDepthRange到以下内容:

glDepthRange(1.0f, 0.0f);

我假设glDepthRange(0.0f,1.0f)会使靠近我的Z_NEAR(-0.1)的物体更接近0,更接近我的Z_FAR(-1000)的物体会更接近1。将我的深度测试设置为GL_LESS是完全合理的,事实上,无论我的Z_NEAR和Z_FAR是什么,因为glDepthRange映射值的方式应该是这样的,如果我没有弄错的话。

我必须弄错,因为这个换行意味着靠近我的物体会在深度缓冲区中存储一个接近1的值,而物体的值也会为0,从而呈现向后的绘制顺序 - 但是肯定的是就像一个魅力。

如果有人能够指出我为什么我的假设是错误的,那么我可能不会考虑我对glsl和深度缓冲的理解。在我完全理解它的基础功能之前,我宁愿不继续我的引擎的进展。

修改: 我的_cameraPerspective矩阵的内容如下: 透视矩阵图

AspectX     0           0               0
0           AspectY     0               0
0           0           1               0
0           0         1/focalLength         0

AspectX为16且AspectY为9.焦距默认为70,但是添加了控件以在运行时更改此值。

由derhass指出,这并没有解释如何通过着色器考虑传递给glOrtho()的任何信息。视口尺寸,由于未使用标准管道和矩阵堆栈,与_inverseScreenDimensions一起考虑。这是一个包含[1 / X_RIGHT,1 / Y_UP,1 / Z_Far,1]的vec4。或者由于缺少变量名,[1 / 1000,1 / 1000,-1 / 1000,1]。

在我的顶点着色器中将屏幕坐标向量乘以这导致X值介于-1和1之间,Y值介于-1和1之间,Z值介于0和1之间(如果对象位于前面相机它有一个负z坐标开始),W为1。

如果我没有弄错的话,这将是达到"设备坐标"的最后一步,然后绘制网格。

请记住原始问题:我知道这不是精简的,我知道我没有使用GLM或所有最常用的库,但我的问题不是"嘿家伙解决了这个问题!"我的问题是:为什么我通过改变来解决这个问题?

2 个答案:

答案 0 :(得分:0)

近距离和远距离是您正在寻找的方向上的近距离和远距离平面的距离,因此通常应该都是正数。负数会将剪裁平面放在视图原点后面,这可能不是你想要的。

答案 1 :(得分:0)

使用以下矩阵作为投影矩阵:

AspectX     0           0               0
0           AspectY     0               0
0           0           1               0
0           0         1/focalLength     0

将完全破坏深度值。

当这个应用于向量(x,y,z,w)^T时,你会得到 z'=zw'=z/focalLength作为剪辑空间组件。在相应的划分之后,您将得到z'/w'的{​​{1}}的NDC z分量,该分量仅为focaldepth ,并且完全独立于眼睛空间z值。因此,您将所有内容投射到相同的深度,这完全解释了您所看到的行为。

This page解释了投影矩阵通常是如何构建的,特别是提供了z值如何映射的许多细节。

使用第gl_Position.z = 0.0001+vertexInScreenSpace.z;行 你实际上得到某种"工作"从那以后的深度,NDC Z坐标将是(0.0001+z')/w' focalLenght * (1+ 0.0001/z),最终至少是眼睛空间z的函数,应该如此。人们可以计算映射实际会产生的nearfar值,但对于这个答案,执行该计算是没有意义的。你应该熟悉计算机图形投影的数学,特别是linaer代数和投影空间。

深度测试反转的原因是由于投影矩阵确实否定了z坐标。通常,视图矩阵的构造方式使得查看方向为-z,投影矩阵的最后一行为(0 0 -1 0),而(0 0 1/focalLength 0)则基本上乘以z由-1生效。