本文为L_Ares个人写作,包括图片皆为个人亲自操作,以任何形式转载请表明原文出处。
第八、第九节的问题还没有完全解决,那么这里就涉及到了深度的问题。本节将主要学习深度。
一、深度的概念
首先要明确的知道,OpenGL中深度到底是什么东西?
深度就是该像素点在3D世界中,距离摄像机的距离,也就是常说的Z值。
在OpenGL中,有顶点缓冲区,有颜色缓冲区,之前的代码也都有涉及到,还有一个经常写,但是没有说明含义的缓冲区——深度缓冲区。
什么是深度缓冲区?
深度缓冲区是一块内存区域,它专门的存储每个像素点(绘制在屏幕上的)深度值。深度值越大(Z值越大),那么该点距离摄像机就越远。
为什么需要深度缓冲区?
因为会出现像素点重复绘制,或者覆盖的问题。在不使用深度测试的时候,如果我们先绘制一个距离摄像机更近一点的物体,再绘制一个距离摄像机较远的物体,因为距离远的物体是后绘制的,就会把先绘制的,距离摄像机更近的物体覆盖遮挡掉。有了深度缓冲区后,距离摄像机的远近就不那么的重要。
事实上,只要存在深度缓冲区,OpenGL都会把像素的深度值写入到深度缓冲区中,除非调用
glDepthMask(GL_FALSE)
在禁止写入
什么是深度测试?
深度缓冲区(DepthBuffer)和颜色缓冲区(ColorBuffer)是具有联系的。当我们需要绘制一个物体表面的时候,OpenGL会把表面对应的像素点的深度值和深度缓冲区中的深度值进行大小比较,如果需要绘制的表面的深度值大于深度缓冲区中存储的深度值,则会丢弃大于深度缓冲区深度值的这部分的绘制。而没有大于的这部分则会将他们的像素值和深度值替换掉原有的深度缓冲区和颜色缓冲区的存储值,这个过程也就是深度测试。
这里有一点很重要!!!深度缓冲区存储的是像素点的深度,是以像素点未单位的,而不是你所绘制的图形的z值,因为你可以绘制很多很多的图形,图形是存在重叠的情况的,那么重叠的地方,用的都是相同的像素点,保存的是距离观察者最近的图形的z值转换成的像素点的深度,而不是图形的z值。
关于深度值到底是怎么计算的,会开辟延展节单独做学习,内容比较多。
二、使用深度测试
深度缓冲区,一般由窗口管理系统GLFW创建,深度值一般由16,24,32位值表示,默认是24位,位数越高,则深度精确度越好。
开启深度测试
glEnable(GL_DEPTH_TEST)
这里要注意的是,如果开启了深度测试,则证明需要用到深度缓冲区,那么在之前的代码中,我们经常使用的glClearColor()
和glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
则是必不可少的。
清除深度缓冲区,其默认值为1.0,表示最大的深度值。
深度值的范围:(0,1)之间。值越小,表示越靠近观察者,值越大则越远离观察者。
另外,当我们退出这个绘制界面的时候,最好把深度测试关闭掉。
深度测试判断式
void glDepthFunc(GLenum mode)
参数选择及含义如下图2.1:
2.1.png打开/阻断深度值的写入
void glDepthMask(GLBool value)
GL_TRUE
:开启深度缓冲区写入。
GL_FALSE
:阻断深度缓冲区写入。
甜甜圈代码
现在我们补全之前的代码,只需要在void RenderScene()
中的开启正背面剔除的下面增加下面的代码即可,因为我们要对比,所以这里添加了if判断。
if (isDepth) {
glEnable(GL_DEPTH_TEST);
}
else{
glDisable(GL_DEPTH_TEST);
}
现在,这个甜甜圈终于不是破轮胎了。
但是这就没问题了嘛?如果深度值重复了,或者说深度值都相同的话,那会不会还有问题呢?
三、ZFighting闪烁问题
先看一下,当OpenGL无法分辨深度值大小的时候可能会产生的后果。如图3.1和图3.2所示:
3.1.png 3.2.png这就是ZFighting问题。
为什么会出现ZFighting问题呢?
因为当OpenGL开启了深度测试之后,OpenGL就不会再去绘制模型被遮挡的部分,但是由于深度值的精确度的限制,对于深度值相差非常非常小的情况下,OpenGL也无法精确的判断他们的深度值大小,这就会导致深度测试出现不确定性,也就会出现这种交错闪烁的情况。
如何解决ZFighting问题?
思路也不难,产生这个问题的原因是深度值差别太小,那么我们给他们之间人为添加一个空隙,骗过OpenGL,这就是多边形偏移方法。
而且这个空隙都不需要由我们来计算,OpenGL有提供方法如下:
第一步:启用Polygon Offset方法解决
glEnable(GL_POLYGON_OFFSET_FILL)
参数可选,主要根据光栅化的模式来选择参数:
GL_POLYGON_OFFSET_FILL
对应光栅化模式:GL_FILL
GL_POLYGON_OFFSET_POINT
对应光栅化模式:GL_POINT
GL_POLYGON_OFFSET_LINE
对应光栅化模式:GL_LINE
第二步:指定偏移量
-
通过
void glPolygonOffset(GLFloat factor,GLFloat units)
来指定。 -
每个片元的深度值增加的偏移量如下:
Offset = (m * factor) + (r * units)
m:多边形的深度的斜率的最大值,多边形越是与近剪裁面平行,m的值也就越接近0。
r:能产生于窗口坐标系中的可分辨的差异最小值,r是一个具体的值,是由OpenGL平台分配的一个常量。
-
一个大于0的Offset会把模型推到离观察者(摄像机)更远的位置,而小于0的Offset会把模型拉近观察者(摄像机)
-
一般情况下,只需要将-1.0和-1赋值给
glPolygonOffset
即可。
关闭偏移量
用完了记得关。
glDisable(GL_POLYGON_OFFSET_FILL)
如何预防ZFighting问题?
-
首先就是不要把两个物体靠的太近。可以手动添加一个很小的偏移量,但是不建议,因为如果绘制的物体很多,本身距离又存在相差不大的情况,手动添加偏移量只可能解决这一个的问题,但是可能会造成新的问题。
-
近剪裁面离观察者远一点,因为更远的情况下,深度值的精度要求也就更高,但是这种方法可能造成离观察者近的物体随着距离增加而剪裁,所以重点是调试好剪裁面参数。
-
刚才说了深度缓冲区的位数有三种,位数越高,精度越高。16、24、32,默认是24,既然24的不行,那就换32位深度缓冲区。
四、剪裁
理解即可。
关于窗口、视口、剪裁区域
先确定这三者的关系,下面我用一张图来说明,如图4.1所示:
4.1.png-
窗口
就是项目的显示界面 -
视口
窗口中用来显示图形的一块区域,一般情况下和窗口内平面等大。当然也是可以比窗口大或者小。
这里要注意,超出视口的图形部分是不予显示的。
我们可以通过glViewport(GLint x, GLint y, GLsizei width, GLsizei height)
函数设置视口的x,y,w,h -
剪裁区域
如图4.1所示,可以通过void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar)
函数来设置。
这里的left和right是用视口矩形区域的最大和最小x坐标,bottom和top也一样,只不过用的是y坐标,千万别用窗口的。也可以指定最近和最远的z坐标,这样就会变成一个立体的剪裁区域。
定义
在OpenGL中提高渲染的一种方式,只刷新屏幕上发生变化的部分。
OpenGL可以做到让要发生渲染的窗口只指定一个剪裁框。
原理
用于渲染时限制绘制区域,可以在屏幕上指定一个矩形区域进行绘制,其他的不更新绘制。启用剪裁测试后,不在矩形区域内的片元被丢弃,只有在矩形区域内的片元才有可能进入帧缓冲。
就像在屏幕上画出来一块矩形区,然后在矩形区域中可以进行指定内容的绘制。
使用方法
- 开启剪裁测试
glEnable(GL_SCISSOR_TEST)
- 关闭剪裁测试
glEnable(GL_SCISSOR_TEST)
- 指定剪裁窗口
void glScissor(GLint x,GLint y,GLsizei width,GLsizei height)
x,y分别是指定的剪裁窗口的左下角的横纵坐标值。width,height就是宽高,用来设置剪裁框的尺寸。
至此,关于深度的问题也学习完成了,圆环也绘制成功了。
网友评论