android openGL ES详解——深度缓冲区

目录

一、深度缓冲区概念

二、深度概念

三、深度缓冲原理

四、深度测试

1.深度测试概念

2.深度值精度

3.深度测试常用API

4.所带来的弊端

五、深度缓冲区案例讲解

六、如何使用深度缓冲?

七、深度冲突——Z冲突(Z-Fighting,闪烁问题)

1.深度冲突概念

2.案例分析

八、深度冲突的预防

1.避免两个物体靠的太近

2.适度放远一些近平面

3.提高深度缓冲区的精确度


一、深度缓冲区概念

深度缓存区是指一块专门内存区域,存储在显存中,用于存储屏幕上所绘制图形的每个像素点的深度值。深度值越大,离观察者越远。深度值越小,离观察者越近。

深度缓冲区与帧缓冲区相对应,用于记录上面每个像素的深度值,通过深度缓冲区,我们可以进行深度测试,从而确定像素的遮挡关系,保证渲染正确。

二、深度概念

深度,指OpenGL坐标系中,像素点的Z坐标距观察者的距离。其实就是该象素点在3d世界中距离摄象机的距离(绘制坐标),深度缓存中存储着每个象素点(绘制在屏幕上的)的深度值!深度值(Z值)越大,则离摄像机越远。

为什么需要深度?

在不使用深度测试的时候,如果我们先绘制一个距离较近的物体,再绘制距离较远的物体,则距离远的物体因为后绘制,会把距离近的物体覆盖掉,这样的效果并不是我们所希望的。而有了深度缓冲以后,绘制物体的顺序就不那么重要了,都能按照远近(Z值)正常显示,这很关键。

三、深度缓冲原理

       深度缓冲区原理就是把一个距离观察平面(近裁剪面)的深度值(或距离)与窗口中的每个像素相关联。将深度值与屏幕上的每个像素点进行一一对应,然后将深度值存储到深度缓冲区。
       首先,使用glClear(GL_DEPTH_BUFFER_BIT),把所有像素的深度值设置为最大值(一般是远裁剪面)。然后,在场景中以任意次序绘制所有物体。硬件或者软件所执行的图形计算把每一个绘制表面转换为窗口上一些像素的集合,此时并不考虑是否被其他物体遮挡。

      其次,OpenGL会计算这些表面和观察平面的距离。如果启用了深度缓冲区,在绘制每个像素之前,OpenGL会把它的深度值和已经存储在这个像素的深度值进行比较。新像素深度值<原先像素深度值,则新像素值会取代原先的;反之,新像素值被遮挡,他颜色值和深度将被丢弃。

      为了启动深度缓冲区,必须先启动它,即glEnable(GL_DEPTH_TEST)。每次绘制场景之前,需要先清除深度缓冲区,即glClear(GL_DEPTH_BUFFER_BIT),然后以任意次序绘制场景中的物体。

四、深度测试

1.深度测试概念

深度缓冲区(DepthBuffer)和颜色缓冲区(ColorBuffer)是对应的,颜色缓冲区是存储像素的颜色信息,而深度缓冲区存储像素的深度信息。在确定是否绘制一个物体表面的时候,首先要将表面对应的像素深度值与当前深度缓冲区中的值进行比较,如果大于深度缓冲区的值,则丢弃这部分。否则利用这个像素对应的深度值和颜色值分别更新深度缓冲区和颜色缓冲区,这个过程称为深度测试

2.深度值精度

      深度值一般由16位、24位或者32位值表示,通常是24位,位数越高的话,深度的精确度越高,深度值的范围在[0,1]之间,值越小表示月靠近观察者,值越大表示远离观察者。

      深度缓冲主要是通过计算深度值来比较大小,在深度缓冲区中包含深度值,介于0.0~1.0之间,从观察者看到其内容与场景中的所有对象的z值进行了比较,这些视图中的z值可以投影平头截体的近平面和远平面之间的任意值。我们因此需要些方法转换这些视图空间的z值到[0,1]的范围内,下面的线性方程把z值转换为0.0~1.0之间的值。

这里far和near是我们用来提供到投影矩阵设置可见视图截锥的远近值 (见坐标系)。方程带内锥截体的深度值 z,并将其转换到 [0,1] 范围。在下面的图给出 z 值和其相应的深度值的关系:

注意在物体接近近平面的时候,方程给出的深度值接近0.0,物体接近远平面时,方程给出的深度接近1.0。

然而,在实践中是几乎从来不使用这样的线性深度缓冲区。正确的投影特性的非线性深度方程是和1/z成正比的 。这样基本上做的是在z很近是的高精度和 z 很远的时候的低精度。用几秒钟想一想: 我们真的需要让1000单位远的物体和只有1单位远的物体的深度值有相同的精度吗?线性方程没有考虑这一点。

由于非线性函数是和 1/z 成正比,例如1.0 和 2.0 之间的 z 值,将变为 1.0 到 0.5之间, 这样在z非常小的时候给了我们很高的精度。50.0 和 100.0 之间的 Z 值将只占 2%的浮点数的精度,这正是我们想要的。这类方程,也需要近和远距离考虑,下面给出:

如果你不知道这个方程到底怎么回事也不必担心。要记住的重要一点是在深度缓冲区的值不是线性的屏幕空间 (它们在视图空间投影矩阵应用之前是线性)。值为 0.5 在深度缓冲区并不意味着该对象的 z 值是投影平头截体的中间;顶点的 z 值是实际上相当接近近平面!你可以看到 z 值和产生深度缓冲区的值在下列图中的非线性关系:

正如你所看到,一个附近的物体的小的 z 值因此给了我们很高的深度精度。变换 (从观察者的角度) 的 z 值的方程式被嵌入在投影矩阵,所以当我们变换顶点坐标从视图到裁剪,然后到非线性方程应用了的屏幕空间中。

3.深度测试常用API

  • 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

OpenGL 允许我们修改它深度测试使用的比较运算符(comparison operators)。这样我们能够控制OpenGL通过或丢弃碎片和如何更新深度缓冲区。我们可以通过调用glDepthFunc来设置比较运算符 (或叫做深度函数(depth function))。

在默认情况是将需要绘制的新像素的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当前深度值 != 存储的深度值, 总是通过测试

让我们看看改变深度函数对输出的影响。我们将使用新鲜的代码安装程序显示一个没有灯光的有纹理地板上的两个有纹理的立方体。你可以在这里找到源代码和其着色器代码。

代码中我们将深度函数设为GL_ALWAYS:

glDepthFunc(GL_ALWAYS);

这和我们没有启用深度测试得到了相同的行为。深度测试只是简单地通过,所以这样最后绘制的片段就会呈现在之前绘制的片段前面,即使他们应该在前面。由于我们最后绘制地板平面,那么平面的片段会覆盖每个容器的片段:

重新设置到GL_LESS给了我们曾经的场景:

开启深度测试的方法是:默认是关闭的

glEnable(GL_DEPTH_TEST);

关闭深度测试的方法是:

glDisable(GL_DEPTH_TEST);

4.所带来的弊端

启用了深度测试,那么这就不适用于同时绘制不透明物体。当需要绘制半透明物体时,需注意,在绘制半透明物体时前,还需要利用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);

一旦启用深度测试,如果片段通过深度测试,OpenGL自动在深度缓冲区存储片段的 z 值,如果深度测试失败,那么相应地丢弃该片段。如果启用深度测试,那么在每个渲染之前还应使用GL_DEPTH_BUFFER_BIT清除深度缓冲区,否则深度缓冲区将保留上一次进行深度测试时所写的深度值

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 表示关闭深度缓冲区写入

在某些情况下我们需要进行深度测试并相应地丢弃片段,但我们不希望更新深度缓冲区,基本上,可以使用一个只读的深度缓冲区;OpenGL允许我们通过将其深度掩码设置为GL_FALSE禁用深度缓冲区写入:

glDepthMask(GL_FALSE);

注意这只在深度测试被启用的时候有效。

七、深度冲突——Z冲突(Z-Fighting,闪烁问题)

1.深度冲突概念

两个平面或三角形如此紧密相互平行深度缓冲区不具有足够的精度以至于无法得到哪一个靠前。结果是,这两个形状不断似乎切换顺序导致怪异出问题。这被称为深度冲突(Z-fighting),因为它看上去像形状争夺顶靠前的位置。

2.案例分析

案例一:

我们到目前为止一直在使用的场景中有几个地方深度冲突很显眼。容器被置于确切高度地板被安置这意味着容器的底平面与地板平面共面。两个平面的深度值是相同的,因此深度测试也没有办法找出哪个是正确。

如果您移动摄像机到容器的里面,那么这个影响清晰可见,容器的底部不断切换容器的平面和地板的平面:

深度冲突是深度缓冲区的普遍问题,当对象的距离越远一般越强(因为深度缓冲区在z值非常大的时候没有很高的精度)。深度冲突还无法完全避免,但有一般的几个技巧,将有助于减轻或完全防止深度冲突在你的场景中的出现:

案例二:

ZFighting闪烁的问题

造成ZFighting闪烁问题的原因:是因为开启深度测试后,OpenGL就不会再去绘制模型被遮挡的部分,这样实现显示的更加真实。但是由于深度缓冲区精度的限制对深度相差非常小的情况下,(例如在同一平面上绘制两次深度值一样的图形),openGL就有可能出现不能正确判断两者的深度值得情况,会导致深度测试的结果不可预测,显示出来的现象就是交错闪烁,也就是这两个画面交错出现的情况。如图所示:

其问题产生的主要原因是由于图形靠的太近,导致无法区分出图层先后次序,针对该问题,OpenGL提供了一种多边形偏移(Polygon Offset)方案

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)

八、深度冲突的预防

多边形偏移只是一个解决深度测试隐患的方案,当然能从根源上预防ZFighting闪烁问题才是我们开发中需要注意的。下面是预防深度冲突的三个方案:

1.避免两个物体靠的太近

最重要的技巧是让物体之间不要离得太近,以至于他们的三角形重叠。通过在物体之间制造一点用户无法察觉到的偏移,可以完全解决深度冲突。在容器和平面的条件下,我们可以把容器像+y方向上略微移动。这微小的改变可能完全不被注意但是可以有效地减少或者完全解决深度冲突。然而这需要人工的干预每个物体,并进行彻底地测试,以确保这个场景的物体之间没有深度冲突。

2.适度放远一些近平面

另尽可能把近平面设置得远一些。前面我们讨论过越靠近近平面的位置精度越高。所以我们移动近平面远离观察者,我们可以在椎体内很有效的提高精度。然而把近平面移动的太远会导致近处的物体被裁剪掉。所以不断调整测试近平面的值,为你的场景找出最好的近平面的距离。

3.提高深度缓冲区的精确度

放弃一些性能来得到更高的深度值的精度。大多数的深度缓冲区都是24位。但现在显卡支持32位深度值,这让深度缓冲区的精度提高了一大节。所以牺牲一些性能你会得到更精确的深度测试,减少深度冲突。

我们已经讨论过的 3 个技术是最常见和容易实现消除深度冲突的技术。还有一些其他技术需要更多的工作,仍然不会完全消除深度冲突。深度冲突是一个常见的问题,但如果你将列举的技术适当结合你可能不会真的需要处理深度冲突。


 

参考文章

深度缓冲详解(DepthBuffer)_depth buffer-优快云博客

https://www.cnblogs.com/overxus/p/17898609.html

https://www.cnblogs.com/errorman/p/17222794.html

7、OpenGL初探之OpenGL图像渲染的深度缓冲区理解及隐患解决 - 简书

OpenGL_正面剔除,深度测试和Z冲突 - 简书

深度测试 - LearnOpenGL-CN

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闲暇部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值