当openGL显示立体图形时候,例如甜甜圈,有多个图层时候,需要开启正背面剔除功能,当转到下面这个角度时候,方框内的都是正面
但是到下面角度时候,会导致openGL无法区分哪一块是正面,哪一块是背面 例如
- 1、当甜甜圈旋转时,2个部分重叠时,此时OpenGL不能清楚分辨哪个图层在前,哪个图层在后,则会出现甜甜圈被啃一口的现象;
- 2、隐藏面消除,除了使用正面剔除,还可以使用深度测试来解决
一、深度测试
1、深度测试定义
深度其实就是该像素点在3D世界中距离摄像机的距离,Z值,因为观察者是可以在坐标系的任意方向,可能在Z轴的正方向,也就是正面,也可能在Z轴的反方向,也就是反面
- 当观察者在Z轴方向的正方向,Z值越大则越靠近观察者
- 当观察者在Z轴方向的反方向,Z值越小则靠近观察者
2、深度缓冲区
深度缓冲区,也就是深度缓存区,存储在显存中,专门存储着每个像素点(绘制在屏幕上的)深度值,深度值越大,则离摄像机就越远,就是把距离观察者平面(近裁剪面,也就是图层)的深度值与窗口中每个像素点1对1进行关联以及存储,并且每个像素点对应的深度值都只有一个,大小范围是0-1
清空深度缓冲区
glClear(GL_COLOR_BUFFER_BIT|GLDEPATH_BUGGER_BIT)
其中里面参数:
- GLDEPATH_BUGGER_BIT:深度缓冲区
- GL_COLOR_BUFFER_BIT:颜色缓冲怒气
开启深度测试
glEnable(GL_DEPATH_TEST);
深度缓冲区(DepthBuffer)和颜色缓冲区(colorBuffer)是对应的,颜色缓冲区存储像素的颜色信息,而深度缓冲区存储像素的深度信息,在决定是否绘制一个物体表面时,首先要将表面对应的像素的深度值与当前深度缓冲区的值进行比较,如果大于深度缓冲区汇总的值,则丢弃这部分,否则利用这个像素对应深度值和颜色值,分别更新深度缓冲区和颜色缓存区,这个过程称为“深度测试”
开启了深度测试,则在绘制每个像素之前,OpenGL会把它的深度值与目前像素点对应存储的深度值进行比较,如果像素点对应的深度值小于像素点对应的深度值,也就是比较哪个更接近观察者,那么此时就会将该像素点的深度值取而代之,反之,如果像素点上的新颜色值距离观察者更远就会被遮挡,那么此时对应的深度值和颜色值都会被抛弃,不进行绘制;
关闭深度测试
glDisable(GL_DEPTH_TEST)
因为深度测试是针对全局的,所以当我们不需要深度测试的时候就需要将深度测试给关闭
修改测试规则
glDepthFunc(GLenum func)
测试的规则主要在于该函数中的枚举值:
但是这个规则我们一般不会去动,因为这个规则是作为替换像素点值去使用的,例如上面我们判断假如新图层的深度值大于缓冲区的值,那么我们就更新,这个大于就是测试的规则,一般情况下,我们是不去修改他的
3、深度缓冲区存在的意义
以前渲染时候,我们是遵循油画算法的,也就是先绘制场景中离观察者较远的物体,再绘制较近的物体
例如下面的图例,先绘制红色部分,在绘制黄色部分,最后在绘制灰色部分,这样就解决隐藏面消除的问题
但是这样看不到的画面也绘制了,很浪费性能和时间,为了节省绘制的时间,我们可以开启深度测试,例如下面图案
当绿色图层和黄色图层叠加在一起并且开启了深度测试时候,两个图层叠加的地方因为像素点只能存一份颜色信息,而确定存那个颜色的信息就是通过深度值来确定的,当处理绿图层时候,我们会拿到绿图层像素的深度值跟深度缓冲区对应的像素值的深度进行比较,假如绿色图层像素的深度值比缓冲区的深度值大的话,那我们就更新缓冲区的值后,同时也更新颜色缓冲区的颜色值,这个时候我们就把绿色的颜色值存储在颜色缓冲区了
- 在不使用深度测试的时候,如果我们绘制自一个距离比较近的物体,再绘制距离较远的物体,则距离圆的位图因为是后绘制,会把距离近的物体覆盖掉
- 有了深度缓冲区偶,绘制物体的顺序就不那么重要了,实际上,只要存在深度缓冲区,openGL都会把像素的深度值写入到缓冲区中,除非调用glDepthMask(GL_FALSE)来禁止写入
通过实验我们发现开启了深度测试,就不用开启正背面消除了,因为深度测试已经很完美的符合了展示要求
三、深度测试的ZFighting问题
因为在开启深度测试后,OpenGL就不会再去绘制被遮挡的部分,这样可以让显示的更加真实,但是由于深度缓冲区的精度是有限制的,这样对于深度相差非常小的情况下,openGL就可能出现不能正确判断两者的深度值,会导致深度测试的结果不可以预测,显示出来的现象是交错闪烁的两个画面。
解决办法
既然因为考的太近,无法区分图层先后,那么此时就可以在两个图层之间加入一个微妙的间隔,那么手动添加,复杂且不精确,此时OpenGL提供了一个解决方案,多边形偏移
1、启动多边形偏移
让深度值之间产生间隔,如果2个图形之间有间隔,是不是意味着就不会产生干涉,可以理解为在执行深度测试前将立方体的深度值做一些细微的增加,于是就能将重叠的2个图形深度值之间有所区分
> glEnable(GL_POLYGON_OFFSET_FILL)
参数列表:
GL_POLYGON_OFFSET_POINT 对应模式:GL_POINT
GL_POLYGON_OFFSET_LINE 对应模式:GL_LINE
GL_POLYGON_OFFSET_FILL 对应模式:GL_FILL
2、指定偏移
- 通过glPolygonOffset来指定,glPolygonOffset需要2个参数:factor、units
- 每个Fragment的深度值都会增加如下所示的偏移量
Offset = (m * factor)+(r * untis);
- m:多边形的深度的斜率的最大值,可以理解为一个多边形约束与近裁剪面平行,m就越接近于0.
- r:能产生窗口坐标系的深度值中课分辨的差异最小值,r是由具体OpenGL平台指定的一个常量
- 一个大于0的Offset会把模型推到离你(摄像机)更远的位置,相应的一个小于0的Offset会把模型拉进
- 一般而已,只需将-1和1这样简单赋值给glPolygonOffset基本可以满足需求
3、关闭多边形偏移
glDisable(GL_POLYGON_OFFSET_FILL)
4、如何预防ZFighting
- 不要将两个物体靠太近,避免渲染时三角形叠在一起。这种方式要求对场景中物体插入一个少量的偏移,那么就可能避免ZFigting现象。例如上面的立方体和平面问题中,将平面下移0.001f就可以解决这个问题。当然手动去插入这个小的偏移量是要付出代价的
- 尽可能将近裁剪面设置的离观察者远一点。上面我们看到,在近裁剪平面附近,深度的精确度是很高的,因此尽可能让近裁剪面远一些的话,会让整个裁剪范围内的精确度变高一些。但是这种方式会使离观察者较近的物体被裁减掉,因此需要调试好裁剪面参数
- 使用跟高位数的深度缓冲区,也就是用更好的硬件设备。通常使用的深度缓冲区是24位。现在有一些硬件使用32/64位的缓冲区,使精确度得到提高
四、透明图层的处理--混合
我们把OpenGL渲染时会把颜色值存在颜色缓存区中,每个片段的深度值也是放在深度缓冲区。当深度缓冲区被关闭时,新的颜色将简单的覆盖原来颜色缓存区的颜色值,当深度缓冲区再次打开时,新的颜色片段只是当它们比原来的值更接近邻近的裁剪平面才会替换原来的颜色片段。
那么如果开启深度测试后,但是2个重叠的图层中,有一个图层是半透明的,有一个图层是非半透明的,那么此时就不能单纯的比较深度值,然后进行覆盖,而是需要将2个图层的颜色进行混合。
公式使用场景
- 正常渲染,当我们用固定着色器或者可编程着色器,可以使用开关方式,来进行单纯的两个图层重叠和混合
- 特效,当我们用可编程着色器,也就是片元着色器时候,处理图片原图颜色时候可以用这个公式进行方程式计算得到最终的颜色
1、颜色混合公式
我们来看一下颜色混合公式:
Cf = (Cs * S) + (Cd * D)
Cf:最终计算的颜色
Cs:源颜色:将要加入颜色缓冲区的颜色值
S:源颜色的混合因子
Cd:目标颜色:已经存在颜色缓冲区的颜色值
D:目标颜色混合因子
这个公式中Cf和Cs不用我们考虑,因为我们也不知道图层上到底是什么颜色,而我们只需要设置一下混合因子
2、设置混合因子
glBlendFunc(GLenum S,GLenum D)
S:源混合因子
D:目标混合因子
- 下面表中R、G、B、A分别代表红、绿、蓝、alpha
- 表中下标S、D,分别代表源、目标
- 表中C代表常量颜色(默认是黑色)
举个例子来说明一下混合后函数的使用
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
如果颜色缓冲区已经有一种颜色红色(1.0f,0.0f,0.0f,1.0f),这个目标颜色Cd,如果在这上面用一种alpha为0.6的蓝色(0.0f,0.0f,1.0f,0.6f)
Cd(目标颜色)= (1.0f,0.0f,0.0f,1.0f);
Cs(源颜色) = (0.0f,0.0f,1.0f,0.6f);
S = 源alpha值 = 0.6f;
D = 1 - 源alpha值 = 1 - 0.6f = 0.4f
方程式Cf = (Cs * S) + (Cd * D)
等价于 = (Blue * 0.6f) + (Red * 0.4f)
总结:
从刚刚例子我们看到:
- 最终颜色是以原来的红色(目标颜色)与后来的蓝色(源颜色)进行组合
- 源颜色的alpha值越高,添加的蓝色颜色成分越高,目标颜色所保留的成分就会越少
- 混合函数经常用于实现再其他一些不透明的物体前面绘制一个透明物体的效果
- 对于上面公式,我们只需将参数套进去就行了,具体计算方式由系统去计算
- 并且我们改动混合因子,因为方程式相同,改变因子只是改变权重而已,所以对最终结果影响很小,肉眼基本感觉不出来
- 当有多个图层透明时候,系统只会拿到存在颜色缓存区的颜色进行混合,所以混合对于多个图层依然有效
3、其他颜色混合公式
默认的混合方程式:
Cf = (Cs * S) + (Cd * D)
我们可以从5个不同的方程式中进行选择
//选择混合方程式的函数
glbBlendEquation(GLenum mode)