一、深度测试
1.1 深度测试解决什么问题
- 由前一篇OpenGL--如何使用正背面剔除解决问题博客,我们知道,我们在绘制甜甜圈之后,当我们旋转时,2个部分会出现重叠的现象,此时OpenGL 不能清楚分辨哪个图层在前哪个图层在后,则会出现甜甜圈被啃⼀⼝的现象;
- 上一篇我们使用的是正背面剔除来解决隐藏面消除的问题,本篇文章是剖析另外一种方法使用深度测试来解决这个问题。
1.2 什么是深度
- 深度就是在openGL坐标系中,像素点的 Z 坐标距离观察者的距离。(深度其实就是该像素点在3D世界中距离摄像机的距离,Z值)
- 当观察者可以放在坐标系的任意位置, 所以. 不能简单的说Z 数值越⼤或越⼩. 观察者就越靠近物体。
关于深度该如何理解:
1,如果观察者在Z轴的正⽅向, Z值越⼤则靠近观察者;
2,如果观察者在Z轴的负⽅向, Z值越⼩则靠近观察者;
1.3 深度缓存区
- 什么是深度缓存区
深度缓存区,就是⼀块内存区域,专⻔存储着每个像素点(绘制在屏幕上的)深度值.深度值(Z值)越⼤,则离摄像机就越远。 - 深度缓存区在什么位置?其实它就存储在显存中;
- 深度缓存区的原理解析:深度缓存区就是把距离观察者平⾯(近裁剪⾯)的深度值与窗口中的像素点进行一对一的关联并且存储 起来。比如分辨率为120x120的图片,它的深度值就是120*120=14400。
- 为什么需要深度缓存区
在不使⽤深度测试的时候,如果我们先绘制⼀个距离⽐较近的物理,再绘制距离较远的物理,则距离远的位图因为后绘制,会把距离近的物体覆盖掉. 有了深度缓冲区后,制物体的顺序就不那么重要的. 实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写⼊到缓冲区中. 除⾮调⽤glDepthMask(GL_FALSE)
来禁⽌写⼊。
1.3.1什么是深度测试
- 首先我们要知道深度缓存区和颜色缓存区是一一对应的;颜色缓存区存储的是颜色信息,而深度缓存区存储的是像素的信息。在绘制一个物体的表面的时候,我们首先要将表面对应的像素的深度值与当前深度缓存区中的值进行比较,如果出现了大于深度缓存区中的值,则这一部分丢弃,否则利用这个像素对用的深度值和颜色值,更新各自对应的缓存区的值,这个过程叫做深度测试。
注意:深度缓存区的默认值为1.0,并且1.0表示是最大的深度值;深度值的范围是[0,1]之间。
- 如何计算深度值
1.深度值⼀般由16位,24位或者32位值表示,通常是24位。位数越⾼的话,深度的精确度越好。深度值的范围在[0,1]之间,值越⼩表示越靠近观察者,值越⼤表示远离观察者。
2.深度缓冲主要是通过计算深度值来⽐较⼤⼩,在深度缓冲区中包含深度值介于0.0和1.0之间,从观察者看到其内容与场景中的所有对象的 z 值进⾏了⽐较。这些视图空间中的 z 值可以在投影平头截体的近平⾯和远平⾯之间的任何值。我们因此需要⼀些⽅法来转换这些视图空间 z 值 到 [0,1] 的范围内,下⾯的 (线性) ⽅程把 z 值转换为 0.0 和 1.0 之间的值。
1.4 深度缓存区相关操作流程
- 首先在绘制场景前,清楚颜色缓存区和深度缓存区,清除深度缓冲区默认值为1.0,表示最⼤的深度值,深度值的范围为(0,1)之间。值越⼩表示越靠近观察者,值越⼤表示越远离观察者。
glClearColor(0.0f,0.0f,0.0f,1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- 清空深度缓存区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- 开启深度测试
glEnable(GL_DEPTH_TEST);
- 关闭深度测试
glDisable(GL_DEPTH_TEST);
- 深度测试的判断模式
glDepthFunc(GLEnum mode);
- 打开/阻断 深度缓存区写⼊
glDepthMask(GLBool value);
value : GL_TURE 开启深度缓冲区写⼊;
GL_FALSE 关闭深度缓冲区写⼊
二、多边形偏移
2.1 引起Z-fighting
(Z冲突.闪烁)的原因
因为开启深度测试后,OpenGL 就不会再去绘制模型被遮挡的部分,这样实现的显示更加真实;但是由于深度缓冲区精度的限制对于深度相差⾮常⼩的情况下,OpenGL 就可能出现不能正确判断两者的深度值的情况,就会导致深度测试的结果变得不可预测,显示出来的现象变成了交错闪烁的一个现象;如下图所示
前⾯2个画⾯会出现交错的现象,同一个位置上出现的图层并且深度值出现精度值很低的情况下,就会引起Z冲突、闪烁的现象。简单点来说就是当表示2个物体靠的⾮常的近的时候,OpenGL⽆法确定谁在前,谁在后的问题,从⽽出现显示歧义的问题。
2.2 如何解决Z-fighting
的问题
既然是因为物体靠的太近,⽆法区分图层谁在前谁在后,那么这个时候,我们就可以在2个图层之间加⼊⼀个微妙的间隔,但是在添加间隔的时候如果是⼿动添加,会比较复杂且不精确。此时OpenGL 提供⼀个解决⽅案,多边形偏移
来解决这个问题。
2.2.1 如何启用多边形偏移 Polygon Offset
如果两个图层之间本来就有间隔,是不是两个图层间就不会互相影响了呢?我们可以理解为,在执行深度测试功能之前,我们已经对立方体的深度值做了一些细微的增加,这样就可以将两个图层的深度值做到有所区分。
- 启⽤
Polygon Offset
⽅式
glEnable(GL_POLYGON_OFFSET_FILL);
//参数列表:
//GL_POLYGON_OFFSET_POINT 对应模式: GL_POINT GL_POLYGON_OFFSET_LINE 对应模式: GL_LINE GL_POLYGON_OFFSET_FILL 对应模式: GL_FILL
- 关闭多边形偏移
glDisable(GL_POLYGON_OFFSET_FILL);
2.2.2 指定偏移量
- 通过
过glPolygonOffset
来指定偏移量,过glPolygonOffset
方法需要两个参数factor , units
;
- 每个
Fragment
的深度值都会增加如下所示的偏移量:
Offset = ( m * factor ) + ( r * units);
m
: 多边形的深度的斜率的最⼤值,理解⼀个多边形越是与近裁剪⾯平⾏,m
就越接近于0。
r
: 产⽣于窗⼝坐标系的深度值中可分辨的差异最⼩值。r
是由具体是由OpenGL 平台指定的 ⼀个常量。 - ⼀个⼤于0的Offset 会把模型推到离你(摄像机)更远的位置,相应的⼀个⼩于0的Offset 会把模型拉近。
- ⼀般⽽⾔,只需要将-1 和 -1 这样简单赋值给glPolygonOffset 基本可以满⾜需求。
2.2.3如何使用多边形偏移
- 启用Polygon Offset ⽅式解决
glEnable(GL_POLYGON_OFFSET_FILL)
//参数列表:
//GL_POLYGON_OFFSET_POINT 对应光栅化模式: GL_POINT
//GL_POLYGON_OFFSET_LINE 对应光栅化模式: GL_LINE
//GL_POLYGON_OFFSET_FILL 对应光栅化模式: GL_FILL
- 指定偏移量
glPolygonOffset(Glfloat factor,Glfloat units);
//一般设置factor和units为-1,-1
- 关闭Polygon Offset
glDisable(GL_POLYGON_OFFSET_FILL);
2.2.4如何预防Z-fighting
闪烁问题
- 不要将两个物体靠的太近,避免渲染时三⻆形叠在⼀起。这种⽅式要求对场景中物体插⼊⼀个少量的偏移,那么就可能避免ZFighting现象。例如上⾯的⽴⽅体和平⾯问题中,将平⾯下移0.001f就可以解决这个问题。当然⼿动去插⼊这个⼩的偏移是要付出代价的。
- 尽可能将近裁剪⾯设置得离观察者远⼀些。上⾯我们看到,在近裁剪平⾯附近,深度的精确度是很⾼的,因此尽可能让近裁剪⾯远⼀些的话,会使整个裁剪范围内的精确度变⾼⼀些。但是这种⽅式会使离 观察者较近的物体被裁减掉,因此需要调试好裁剪⾯参数。
- 使⽤更⾼位数的深度缓冲区,通常使⽤的深度缓冲区是24位的,现在有⼀些硬件使⽤使⽤32/64位的缓冲区,使精确度得到提⾼
三、颜色混合原理
3.1 为什么需要颜色混合
OpenGL在渲染的时候会把颜色值存储到颜色缓存区中,每个片段的深度值也会被放入深度缓存区中,当深度缓存区被关闭时候,新的颜色值将会覆盖原来的颜色缓存区中存在的颜色值,当深度缓存区再次打开的时候,新的颜色片段只是当他们比原来的值更接近临近的裁剪平面才会替换为原来的颜色片段。
- 那么根据上面的情况,如果开启深度测试后,有2个重叠的图层中,有一个是半透明的,有一个是非半透明的,那么这个时候就不能进行单纯的比较深度值,然后进行覆盖了,而是需要我们将这2个图层的颜色进行混合计算。
- 理解目标颜色和源颜色
我们可以比作前女友和现女友的关系,目标颜色是前女友,源颜色则为现女友。
目标颜色
:已经存储在颜色缓存区的颜色值。
源颜色
:作为当前渲染命令结果进入颜色缓存区的颜色值。
3.2 颜色混合的流程
我们可以简单的将颜色混合流程表述为:
-
2个单纯的图层进行重叠
固定着色器/可编程着色器->使用开关的方式打开 ->颜色混合; -
多个图层进行混合
假如有三个图层编号为A、B、C,A图层先和B图层进行混合->混合后存储混合后的颜色(保存在颜色缓存区)->与C图层进行混合
3.3颜色混合代码实现
- 开启颜色混合
glEnable(GL_BlEND);
当混合图层被开启的时候源颜⾊和⽬标颜⾊的组合⽅式是混合⽅程式控制的。在默认情况下,混合⽅程式如 下所示:
Cf = (Cs * S) + (Cd * D)
Cf :最终计算参数的颜⾊
Cs : 源颜⾊
Cd :⽬标颜⾊
S:源混合因⼦
D:⽬标混合因⼦
- 设置混合因子,需要⽤到glBlendFun函数
glBlendFunc(GLenum S,GLenum D);
S: 源混合因⼦
D:⽬标混合因⼦
- 混合因子可以有OpenGL提供的表格进行查找
表中R、G、B、A 分别代表 红、绿、蓝、alpha。
表中下标S、D,分别代表源、⽬标
表中C 代表常量颜⾊(默认⿊⾊)
举例说明:下面通过一个常见的混合函数组合来说明问题
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
如果颜⾊缓存区已经有⼀种颜⾊红⾊(1.0f,0.0f,0.0f,0.0f)
这个⽬标颜⾊Cd
,如果在这上⾯⽤⼀种 alpha
为0.6
的蓝⾊(0.0f,0.0f,1.0f,0.6f)
Cd (⽬标颜⾊) = (1.0f,0.0f,0.0f,0.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.3修改颜⾊混合⽅程式
我们知道颜色混合默认的方程式为Cf = (Cs * S) + (Cd * D)
,那么我们有没有可能对这个方程式进行修改呢?答案是肯定可以的,下面就来说说如何修改颜色的混合方程式,OpenGL给我们提供了5个不同的方程式进行选择,并且提供了混合方程式选择的函数glbBlendEquation(GLenum mode);
,我们可以根据下面的表格来选择颜色混合的方程式。