OpenGL学习脚印:深度测试(depth testing)

本文介绍了OpenGL中的深度测试,用于解决3D场景中隐藏面消除问题。通过深度缓冲区和深度测试,可以决定哪个物体表面对观察者可见。文章详细阐述了深度缓冲区的工作原理,包括开启深度测试、可视化深度值和解决深度值精确度问题(如ZFighting)。同时,提出了预防ZFighting的三种方法,并提供了相关参考资料。
摘要由CSDN通过智能技术生成

写在前面
上一节我们使用AssImp加载了3d模型,效果已经令人激动了。但是绘制效率和场景真实感还存在不足,接下来我们还是要保持耐心,继续学习一些高级主题,等学完后面的高级主题,我们再次来改进我们加载模型的过程。本节将会学习深度测试,文中示例程序源代码均可以在我的github下载

本节内容整理自
1.www.learnopengl.com Depth testing
2.depth buffer faq
3.Z buffer 和 W buffer 簡介

通过本节可以了解到

  • 为什么需要深度缓冲区?
  • OpenGL中怎么使用深度缓冲区 ?
  • 可视化深度值
  • 深度值的精度问题-ZFighting

问题背景

在绘制3D场景的时候,我们需要决定哪些部分对观察者是可见的,或者说哪些部分对观察者不可见,对于不可见的部分,我们应该及早的丢弃,例如在一个不透明的墙壁后的物体就不应该渲染。这种问题称之为隐藏面消除(Hidden surface elimination),或者称之为找出可见面(Visible surface detemination)。

解决这一问题比较简单的做法是画家算法(painter’s algorithm)。画家算法的基本思路是,先绘制场景中离观察者较远的物体,再绘制较近的物体。例如绘制下面图中的物体(来自Z buffer 和 W buffer 簡介),先绘制红色部分,再绘制黄色,最后绘制灰色部分,即可解决隐藏面消除问题。

画家算法举例

使用画家算法时,只要将场景中物体按照离观察者的距离远近排序,由远及近的绘制即可。画家算法很简单,但另一方面也存在缺陷,例如下面的图中,三个三角形互相重叠的情况,画家算法将无法处理:

画家算法失效

解决隐藏面消除问题的算法有很多,具体可以参考Visible Surface Detection。结合OpenGL,我们使用的是Z-buffer方法,也叫深度缓冲区Depth-buffer。

深度缓冲区(Detph buffer)同颜色缓冲区(color buffer)是对应的,颜色缓冲区存储的像素的颜色信息,而深度缓冲区存储像素的深度信息。在决定是否绘制一个物体的表面时,首先将表面对应像素的深度值与当前深度缓冲区中的值进行比较,如果大于等于深度缓冲区中值,则丢弃这部分;否则利用这个像素对应的深度值和颜色值,分别更新深度缓冲区和颜色缓冲区。这一过程称之为深度测试(Depth Testing)。在OpenGL中执行深度测试时,我们可以根据需要指定深度值的比较函数,后面会详细介绍具体使用。

OpenGL中使用深度测试

深度缓冲区一般由窗口管理系统,例如GLFW来创建,深度值一般由16位,24位或者32位值表示,通常是24位。位数越高的话,深度的精确度越好。前面我们已经见过了如何在OpenGL中使用深度测试,这里复习下过程。首先我们需要开启深度测试,默认是关闭的:

   glEnable(GL_DEPTH_TEST);

另外还需要在绘制场景前,清除颜色缓冲区时,清除深度缓冲区:

   glClearColor(0.18f, 0.04f, 0.14f, 1.0f);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

清除深度缓冲区的默认值是1.0,表示最大的深度值,深度值的范围在[0,1]之间,值越小表示越靠近观察者,值越大表示远离观察者。
上面提到了在进行深度测试时,当前深度值和深度缓冲区中的深度值,进行比较的函数,可以由用户通过glDepthFunc指定,这个函数包括一个参数,具体的参数如下表所示:

函数 说明
GL_ALWAYS 总是通过测试
GL_NEVER 总是不通过测试
GL_LESS 在当前深度值 < 存储的深度值时通过
GL_EQUAL 在当前深度值 = 存储的深度值时通过
GL_LEQUAL 在当前深度值 <= 存储的深度值时通过
GL_GREATER 在当前深度值 > 存储的深度值时通过
GL_NOTEQUAL 在当前深度值 不等于 存储的深度值时通过
GL_GEQUAL 在当前深度值 >= 存储的深度值时通过

例如我们可以使用GL_AWALYS参数,这与默认不开启深度测试效果是一样的:

  glDepthFunc(GL_ALWAYS);

下面我们绘制两个立方体和一个平面,通过对比开启和关闭深度测试来理解深度测试。
当关闭深度测试时,我们得到的效果却是这样的:
没有启用深度测试
这里先绘制立方体,然后绘制平面,如果关闭深度测试,OpenGL只根据绘制的先后顺序决定显示结果。那么后绘制的平面遮挡了一部分先绘制的本应该显示出来的立方体,这种效果是不符合实际的。

我们开启深度测试后绘制场景,得到正常的效果如下:
这里写图片描述

使用深度测试,最常见的错误时没有使用glEnable(GL_DEPTH_TEST);
开启深度测试,或者没有使用glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);清除深度缓冲区。

与深度缓冲区相关的另一个函数是glDepthMask,它的参数是布尔类型,GL_FALSE将关闭缓冲区写入,默认是GL_TRUE,开启了深度缓冲区写入。

可视化深度值

在可视化深度值之前,首先我们要明白,这里的深度值,实际上是屏幕坐标系下的 zwin 坐标,屏幕坐标系下的(x,y)坐标分别表示屏幕坐标系下以左下角(0,0)为起始点的坐标。 zwin 我们如何获取呢? 可以通过着色器的输入变量gl_FragCoord.z来获取,这个gl_FragCoord的z坐标表示的就是深度值。

我们在着色器中以这个深度值为颜色输出:

  // 原样输出
float asDepth()
{
  return gl_FragCoord.z;
}
void main()
{
    float depth = asDepth();
    color = vec4(vec3(depth), 1.0f);
}

输出后的效果如下图所示:
默认深度值可视化

可以看到图中,只有离观察者较近的部分有些黑色,其余的都是白色。这是因为深度值 zwin zeye 是成非线性关系的,在离观察者近的地方,精确度较高, zwin 值都保持在较小范围,成黑色。但是一旦超出一定距离,精确度变小, zwin 值都挤在1.0附近,因此成白色。当我们向后移动,拉远场景与观察者的距离后, zwin 值都落在1.0附近,整个场景都变成白色,如下图所示:
当拉远场景与观察者距离后深度值都变为1.0

作为深度值的可视化,我们能不能使用线性的关系来表达 zwin zeye ? 这里我们做一个尝试,从 zwin z_{eye}$。在投影矩阵和视口变换矩阵一节,我们计算出了相机坐标系下坐标和规范化设备坐标系下坐标之间的关系如下:

zndc=f+nfnzeye2fnfn<
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值