OpenGL深度缓冲区的行为不像预期的那样

OpenGL Depth Buffer Behaving Not As Expected

本文关键字:深度 缓冲区 OpenGL      更新时间:2023-10-16

我一直在为教育追求引擎的开始工作,我遇到了一个我认为我理解的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;
}

这段代码导致所有转换和正常计算(不包括在内)都正确完成,但是我的模型的每个面都在不断地争取高于所有其他面。我唯一没有问题的时候是站在正在绘制的第一个模型内部,然后没有任何闪烁,我可以看到苏珊娜的头部内部,就像我应该能够看到的那样。

经过几周的尝试,我终于找到了一个解决方案,只需要修改/添加两行代码。首先,我将这一行添加到顶点着色器主函数的末尾:

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,aspectty为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或所有最常用的库来解决这个问题,但是我的问题不是"嘿,伙计们,解决这个问题!"我的问题是:为什么我所做的更改可以解决这个问题?

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

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

将完全破坏depth值。

当它应用于向量(x,y,z,w)^T时,你会得到z'=zw'=z/focalLength为夹片空间分量。在透视分割之后,您将最终得到z'/w'的NDC z分量,它只是focaldepth ,完全独立于眼空间z值。所以你把所有的东西都投射到同样的深度,这完全解释了你所看到的行为。

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

gl_Position.z = 0.0001+vertexInScreenSpace.z;行从那时起,你实际上得到了某种"工作"深度,NDC Z线将是(0.0001+z')/w', focalLenght * (1+ 0.0001/z),最后至少是眼距Z的函数,就像它应该的那样。可以计算映射实际产生的nearfar的值,但是执行该计算对于这个答案是毫无意义的。你应该熟悉计算机图形投影的数学知识,特别是线性代数和投影空间。

深度测试被反转的原因是由于你的投影矩阵否定了z坐标。通常,视图矩阵是这样构造的:观看方向是-z,投影矩阵的最后一行是(0 0 -1 0),而(0 0 1/focalLength 0)基本上是将z乘以-1。

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