一、深度缓冲区概念
深度缓存区是指一块专门内存区域,存储在显存中,用于存储屏幕上所绘制图形的每个像素点的深度值。深度值越大,离观察者越远。深度值越小,里观察者越近。
深度缓冲区与帧缓冲区相对应,用于记录上面每个像素的深度值,通过深度缓冲区,我们可以进行深度测试,从而确定像素的遮挡关系,保证渲染正确。
二、深度概念
深度,指OpenGL坐标系中,像素点的Z坐标距观察者的距离。其实就是该象素点在3d世界中距离摄象机的距离(绘制坐标),深度缓存中存储着每个象素点(绘制在屏幕上的)的深度值!深度值(Z值)越大,则离摄像机越远。
为什么需要深度?
在不使用深度测试的时候,如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这样的效果并不是我们所希望的。而有了深度缓冲以后,绘制物体的顺序就不那么重要了,都能按照远近(Z值)正常显示,这很关键。
三、深度缓冲原理
深度缓冲区原理就是把一个距离观察平面(近裁剪面)的深度值(或距离)与窗口中的每个像素相关联。将深度值与屏幕上的每个像素点进行一一对应,然后将深度值存储到深度缓冲区。
首先,使用glClear(GL_DEPTH_BUFFER_BIT),把所有像素的深度值设置为最大值(一般是远裁剪面)。然后,在场景中以任意次序绘制所有物体。硬件或者软件所执行的图形计算把每一个绘制表面转换为窗口上一些像素的集合,此时并不考虑是否被其他物体遮挡。
其次,OpenGL会计算这些表面和观察平面的距离。如果启用了深度缓冲区,在绘制每个像素之前,OpenGL会把它的深度值和已经存储在这个像素的深度值进行比较。新像素深度值<原先像素深度值,则新像素值会取代原先的;反之,新像素值被遮挡,他颜色值和深度将被丢弃。
为了启动深度缓冲区,必须先启动它,即glEnable(GL_DEPTH_TEST)。每次绘制场景之前,需要先清除深度缓冲区,即glClear(GL_DEPTH_BUFFER_BIT),然后以任意次序绘制场景中的物体。
四、深度测试
深度测试概念
深度缓冲区(DepthBuffer)和颜色缓冲区(ColorBuffer)是对应的,颜色缓冲区是存储像素的颜色信息,而深度缓冲区存储像素的深度信息。在确定是否绘制一个物体表面的时候,首先要将表面对应的像素深度值与当前深度缓冲区中的值进行比较,如果大于深度缓冲区的值,则丢弃这部分。否则利用这个像素对应的深度值和颜色值分别更新深度缓冲区和颜色缓冲区,这个过程称为深度测试。
深度值计算
深度值一般由16位、24位或者32位值表示,通常是24位,位数越高的话,深度的精确度越高,深度值的范围在[0,1]之间,值越小表示月靠近观察者,值越大表示远离观察者。
深度缓冲主要是通过计算深度值来比较大小,在深度缓冲区中包含深度值,介于0.0~1.0之间,从观察者看到其内容与场景中的所有对象的z值进行了比较,这些视图中的z值可以投影平头截体的近平面和远平面之间的任意值。我们因此需要些方法转换这些视图空间的z值到[0,1]的范围内,下面的线性方程把z值转换为0.0~1.0之间的值。
- glDepthRange
深度测试是采用深度缓存器算法,消除场景中的不可见面。在默认情况下,深度缓存中深度值的范围在0.0到1.0之间,这个范围值可以通过函数:
glDepthRange (nearNormDepth, farNormalDepth);
将深度值的范围变为nearNormDepth到farNormalDepth之间。这里nearNormDepth和farNormalDepth可以取0.0到1.0范围内的任意值,甚至可以让nearNormDepth > farNormalDepth。这样,通过glDepthRange函数可以在透视投影有限观察空间中的任意区域进行深度测试。
- glClearDepth
glClearDepth (maxDepth);
参数maxDepth可以是0.0到1.0范围内的任意值。glClearDepth用maxDepth对深度缓存进行初始化,而默认情况下,深度缓存用1.0进行初始化。由于在进行深度测试中,大于深度缓存初始值的多边形都不会被绘制,因此glClearDepth函数可以用来加速深度测试处理。这里需要注意的是指定了深度缓存的初始化值之后,应调用: glClear(GL_DEPTH_BUFFER_BIT); 完成深度缓存的初始化。使用深度测试之前需要清除深度缓冲和颜色缓冲。glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- glDepthFunc
在默认情况是将需要绘制的新像素的z值与深度缓冲区中对应位置的z值进行比较,如果比深度缓存中的值小,那么用新像素的颜色值更新帧缓存中对应像素的颜色值。这种比较测试的方式可以通过函数:
glDepthFunc(func)
其中参数func的值介绍如下表格,其中默认值是GL_LESS。一般来将,使用glDepthFunc(GL_LEQUAL);来表达一般物体之间的遮挡关系。
参数值 | 参数说明 |
GL_ALWAYS | 总是通过测试 |
GL_NEVER | 总是不通过测试 |
GL_LESS(默认值) | 当前深度值 < 存储的深度值,通过测试 |
GL_LEQUAL | 当前深度值 <= 存储的深度值,通过测试 |
GL_GEQUAL | 当前深度值 >= 存储的深度值,通过测试 |
GL_GREATER | 当前深度值 > 存储的深度值,通过测试 |
GL_EQUAL | 当前深度值 = 存储的深度值,通过测试 |
GL_NOTEQUAL | 当前深度值 != 存储的深度值, 总是通过测试 |
开启深度测试的方法是
glEnable(GL_DEPTH_TEST);
关闭深度测试的方法是
glDisable(GL_DEPTH_TEST);
所带来的弊端
启用了深度测试,那么这就不适用于同时绘制不透明物体。当需要绘制半透明物体时,需注意,在绘制半透明物体时前,还需要利用glDepthMask(GL_FALSE)将深度缓冲区设置为只读形式,否则可能出现画面错误。为什么呢,因为画透明物体时,将使用混色,这时就不能继续使用深度模式,而是利用混色函数来进行混合。这一来,就可以使用混合函数绘制半透明物体了。
深入
像素的深度值是由视矩阵和投影矩阵决定的。在近裁平面上的像素深度值为0,在远裁平面上的像素的深度值为1。场景中的每个对象都需进行绘制,通常最靠近相机的像素会被保留,这些对象阻挡了在它们后面的对象的可见性。
深度缓冲通常还包含stencil(模板) bits – 所以深度缓冲又被叫做depth-stencil缓冲。深度缓冲总是32 bits,但可以用不同的方式组合,类似于纹理格式。常用的深度格式是Depth32,这种格式中32 bits都用来存储深度信息。另一个常用格式是DepthFormat.Depth24Stencil8,这种格式中24 bits用于深度计算而8 bits用于模版缓冲(stencil buffer)。
五、深度缓冲区案例讲解
如果我们想要在三维空间里画两个正方形:一个红色的,一个绿色的,而且从人眼的观察角度看,绿色正方形在红色正方形的后面,最后看上去应该是这样的:
要点在于,从观察者的角度看,绿色正方形在红色正方形的后面,因此绿色正方形的一部分被红色正方形遮挡。
然而,在启用深度测试前,正方形的相对位置完全取决于绘制这两个正方形的顺序。如果我们先绘制红色正方形,再绘制绿色正方形,看上去会是这样:
由于绿色正方形是最后绘制的,因此它在屏幕中遮挡了红色正方形,虽然从顶点坐标来看它应该在红色正方形的后面。而如果最后绘制的是红色正方形,看上去就是正确的效果了。
针对这个问题应该怎么办呢?我们需要你,深度缓冲!
深度缓冲的原理其实非常简单,它为窗口内的每个像素点记录一个深度信息,当有新的点绘制在窗口的某个位置时,OpenGL会比较这个点的深度与此位置之前保留的深度大小,默认情况下保留深度较小的那个像素点,也就是距离我们的视角更近的那个点。从而,如果新绘制点的深度小于之前保留的深度大小,则在窗口内绘制这个点,并更新深度信息;否则就忽略这个点。
现在,无论我们先绘制哪个,都能得到正确的视觉效果:
启用深度测试后,对于两正方形像素的重叠部分,由于红色正方形上的点具有更小的深度,因此只有红色部分会被保留。
有些杠精朋友可能会说:“我就是不喜欢用深度测试,我乐意每次计算哪些物体距离视线更近,越近的物体越到最后才画,你能拿我怎样?” 年轻人不要太气盛,看看下面这个:
如果没有启用深度缓冲,看上去会是这样:
先画红色,再画绿色
或者是这样:
先画绿色,再画红色
最后的视觉效果都不太对。
六、如何使用深度缓冲?
分为如下三步骤:
1.使用深度缓冲前,需要先启用深度测试,可以通过以下代码来启用深度测试:
glEnable(GL_DEPTH_TEST);
2. 清空深度缓冲
在每一帧渲染开始时,需要清空深度缓冲,以便重新开始存储新的深度信息。可以使用以下代码清空深度缓冲:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
为什么需要清空深度缓冲区?
OpenGL 是一种基于状态机的图形渲染API,它使用颜色缓冲区和深度缓冲区来存储渲染结果。当渲染新的帧时,清空这些缓冲区是必要的,以避免渲染结果受到上一帧渲染结果的影响。
清空深度缓冲区是为了确保在绘制新的帧时,深度测试能够正确地工作。深度测试用于确定哪些像素应该显示在其他像素的前面或后面,以创建透视效果。如果不清空深度缓冲区,旧的深度值可能会影响新的帧的深度测试结果,导致渲染错误。
3.设置深度测试函数
深度测试函数指定OpenGL如何比较新像素的深度信息与深度缓冲中已有的深度信息。可以使用以下代码设置深度测试函数:
glDepthFunc(GL_LESS);//指定深度测试判断模式
深度测试函数glDepthFunc参数值介绍如下:
参数值 | 参数说明 |
GL_ALWAYS | 总是通过测试 |
GL_NEVER | 总是不通过测试 |
GL_LESS(默认值) | 当前深度值 < 存储的深度值,通过测试 |
GL_LEQUAL | 当前深度值 <= 存储的深度值,通过测试 |
GL_GEQUAL | 当前深度值 >= 存储的深度值,通过测试 |
GL_GREATER | 当前深度值 > 存储的深度值,通过测试 |
GL_EQUAL | 当前深度值 = 存储的深度值,通过测试 |
GL_NOTEQUAL | 当前深度值 != 存储的深度值, 总是通过测试 |
4.开启/阻断 深度缓冲区的写入
void glDepthMask(GLBool value);
value:
GL_TURE 表示开启深度缓冲区写入
GL_FALSE 表示关闭深度缓冲区写入
七、深度缓冲冲突——Z冲突(Z-Fighting,闪烁问题)
1.ZFighting闪烁的问题
造成ZFighting闪烁问题的原因:是因为开启深度测试后,OpenGL就不会再去绘制模型被遮挡的部分,这样实现显示的更加真实。但是由于深度缓冲区精度的限制对深度相差非常小的情况下,(例如在同一平面上绘制两次深度值一样的图形),openGL就有可能出现不能正确判断两者的深度值得情况,会导致深度测试的结果不可预测,显示出来的现象就是交错闪烁,也就是这两个画面交错出现的情况。如图所示:
其问题产生的主要原因是由于图形靠的太近,导致无法区分出图层先后次序,针对该问题,OpenGL提供了一种多边形偏移(Polygon Offset)方案。
2、ZFighting闪烁问题的问题解决
第一步:启用Polygon Offset(多边形偏移)
解决方法:让深度值之间产生间隔。如果两个图形之间有间隔,是不是就意味着不会产生干涉,可以理解为在执行深度测试前将立方体的深度值做一些细微的调整,于是就能将两个图形的深度值之间有所区分。
启用代码
glEnable(GL_POLYGON_OFFSET_FULL)
参数列表:
GL_POLYGON_OFFSET_POINT 对应点光栅化模式:GL_POINT
GL_POLYGON_OFFSET_LINE 对应线光栅化模式:GL_LINE
GL_POLYGON_OFFSET_FILL对应像素光栅化模式:GL_FILL
第二步:指定偏移量
1、通过glPolygonOffset来指定,glPolygonOffset需要两个参数,factor,units
2、每个Fragment的深度值都会增加如下所示的偏移量
Offset = ( m * factor ) + ( r * units);
m : 多边形的深度的斜率的最大值,理解一个多边形越是与近裁剪面平行,m 就越接近于0.
r : 能产生于窗口坐标系的深度值中可分辨的差异最小值.r 是由具体OpenGL 平台指定的一个常量。
3、一个大于0的Offset会把模型推到离观察者更远的位置,相应的一个小于0的Offset会把模型拉近。
4、一般而言,只需要将-1.0和-1这样简单的值赋值给glPolygonOffset 基本可以满⾜足需求.
void glPolygonOffset(Glfloat factor,Glfloat units);
应⽤用到⽚段上总偏移计算⽅程式:
Depth Offset = (DZ * factor) + (r * units); DZ:深度值(Z值)
r:使得深度缓冲区产生变化的最小值
负值,将使得z值距离我们更近,⽽正值,将使得z值距离我们更远,我们一般设置factor和units为-1,-1。
第三步、关闭Polygon Offset
glDisable(GL_POLYGON_OFFSET_FILL)
具体代码如下:
//1. 在绘制前,开启多边形偏移
glEnable(GL_POLYGON_OFFSET_FILL)
//2. 指定偏移量,参数一般填 -1 和 -1
glPolygonOffset(-1.0,-1.0);
//3. 绘制完成后关闭多边形偏移
glDisable(GL_POLYGON_OFFSET_FILL)
3、ZFighting闪烁问题的预防
讲了这么久,多边形偏移只是一个解决深度测试隐患的方案,当然能从根源上预防ZFighting闪烁问题才是我们开发中需要注意的。下面是预防ZFighting闪烁问题的三个方案
1、避免两个物体靠的太近:在绘制时,插入一个小偏移
不要将两个物体靠的太近,避免渲染时三角形叠在一起。这种方式要求对场景中物体插入一个少量的偏移比如(将其中一个平面向下偏移0.001f),那么就可能避免ZFighting现象。
2、将近裁剪面(设置透视投影时设置)设置的离观察者远一些:提高裁剪范围内的精确度
尽可能将近的裁剪面设置得离观察者远一些。离近裁剪平面越近,深度的精确度会越高 ,因此尽可能让近裁剪⾯远一些,那么整个裁剪范围内的精确度就会变高。但是这种⽅式会使离观察者较近的物体被裁减掉,因此需要调试好裁剪面参数。
3、使用更高位数的深度缓冲区:提高深度缓冲区的精确度
尽量使用更高位数的深度缓冲区,通常使用的深度缓冲区是24位的,现在有一些硬件使用32位的缓冲 区,使精确度得到提高。
参考文章
深度缓冲详解(DepthBuffer)_depth buffer-CSDN博客
https://www.cnblogs.com/overxus/p/17898609.html
https://www.cnblogs.com/errorman/p/17222794.html
7、OpenGL初探之OpenGL图像渲染的深度缓冲区理解及隐患解决 - 简书
OpenGL_正面剔除,深度测试和Z冲突 - 简书