上篇文章提到了 深度测试 可以解决隐藏面消除。这次来深入理解一下深度测试,以及深度测试带来的问题和解决方法:多边形偏移、颜色混合等技巧
一、深度测试
上篇文章提到,由于两个正面叠加在一起,出现了混合, 此时OpenGL不能清楚分辨 哪个图层在前 哪个图层在后,于是就会出现甜甜圈像被啃⼀⼝的现象。我们通过开启深度测试就能解决这个问题。
1、什么是深度?
深度就是在openGL坐标系中,像素点的 Z 坐标距离观察者的距离。
- 因为观察者可以放在坐标系中的任意位置,所以不能说Z的值越大/越小,就代表观察者越靠近物体
- 如果观察者在Z轴的正方向,图形的Z值越大,越靠近观察者
- 如果观察者在Z轴的负方向,图形的Z值越小,越靠近观察者
2、什么是深度缓冲区?
深度缓冲区:就是⼀块内存区域,存储在显存中。专⻔存储着每个像素点(绘制在屏幕上的)深度值,深度值(Z值)越⼤,则离观察者就越远。
原理:把距离观察者平面(近裁剪面)的深度值 和 窗口每个像素点1对1进行关联及存储。
//清空深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
3、为什么需要深度缓冲区?
- 在不使⽤深度测试的时候,如果我们先绘制⼀个距离⽐较近的物体,再绘制距离较远的物体,则距离远的位图因为后绘制,会把距离近的物体覆盖掉。
- 有了深度缓冲区后,绘制物体的顺序就不那么重要了。实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写⼊到缓冲区中. 除⾮调⽤【 glDepthMask(GL_FALSE) 】来禁⽌写⼊。
4、深度测试
深度缓冲区(DepthBuffer)和颜⾊缓冲区(ColorBuffer)是对应的。颜⾊缓冲区存储像素的颜⾊信息,⽽深度缓冲区存储像素的深度信息。
在决定是否绘制⼀个物体表⾯时,⾸先要将表⾯对应的像素的深度值与当前深度缓冲区中的值进⾏⽐较。如果⼤于深度缓冲区中的值(距离观察者更远的图层),则丢弃这部分。否则利⽤这个像素对应的深度值和颜 ⾊值,分别更新深度缓冲区和颜⾊缓存区。这个过程称为”深度测试”
5、深度值计算
- 深度值⼀般由16位,24位或者32位值表示,通常是24位。位数越⾼的话,深度的精确度越
好。深度值的范围在[0,1]之间,值越⼩表示越靠近观察者,值越⼤表示远离观察者。 - 深度缓冲主要是通过计算深度值来⽐较⼤⼩,在深度缓冲区中包含深度值介于0.0和1.0之间,从观察者看到其内容与场景中的所有对象的 z 值进⾏了⽐较。这些视图空间中的 z 值可以在投影平头截体的近平⾯和远平⾯之间的任何值。我们因此需要⼀些⽅法来转换这些视图空间 z 值 到 [0,1] 的范围内,下⾯的 (线性) ⽅程把 z 值转换为 0.0 和 1.0 之间的值
6、深度测试的使用
- 深度缓冲区,⼀般由窗⼝管理系统,GLFW创建.深度值⼀般由16位,24位,32位值表示. 通常是24位.位数越⾼,深度精确度更好
- 清除深度缓冲区默认值为1.0,表示最⼤的深度值,深度值的范围为(0,1)之间. 值越⼩表示越靠近观察者,值越⼤表示越远离观察者
//开启深度测试
glEnable(GL_DEPTH_TEST);
//关闭深度测试
glDisable(GL_DEPTH_TEST);
//在绘制场景前,清除颜⾊缓存区,深度缓冲
glClearColor(0.0f,0.0f,0.0f,1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
指定深度测试判断模式
void glDepthFunc(GLEnum mode);
打开/阻断 深度缓存区写⼊
void glDepthMask(GLBool value);
value : GL_TURE 开启深度缓冲区写⼊; GL_FALSE 关闭深度缓冲区写⼊
7、深度测试潜在风险:Z-fighting 闪烁问题
-
因为开启深度测试后,OpenGL 就不会再去绘制模型被遮挡的部分. 这样实现的显示更加真实.但是由于深度 缓冲区精度的限制对于深度相差⾮常⼩的情况下.(例如在同⼀平⾯上进⾏2次制),OpenGL 就可能出现不能 正确判断两者的深度值,会导致深度测试的结果不可预测.显示出来的现象时交错闪烁.的前⾯2个画⾯,交错出现.
如上图,同⼀个位置上 出现的图层,且深度值出现精确度很低时,就会容易引起 ZFighting 现象. 表示2个物体靠的⾮ 常的近,⽆法确定谁在前,谁在后. ⽽出现显示歧义;
那么,如何解决这个问题呢?
既然是因为靠的太近,⽆法区分图层先后. 那么此时,就可以在2个图层之间加⼊⼀个微妙的间隔. 那么⼿动 添加,复杂且不精确. 此时OpenGL 提供⼀个解决⽅案, "多边形偏移"如何预防ZFighting闪烁问题
1、避免两个物体靠的太近:在绘制时,插入一个小偏移(例如:0.001f就可以解决上面问题)
2、将近裁剪面(设置透视投影时设置)设置的离观察者远一些:提高裁剪范围内的精确度
3、使用更高位数的深度缓冲区:提高深度缓冲区的精确度(现在大多是24位,提升到32/64位)
二、多边形偏移
1、启⽤多边形偏移 Polygon Offset
苹果针对Z-fighting的解决⽅法:让深度值之间产⽣间隔。如果2个图形之间有间隔,是不是意味着就不会产⽣⼲涉。可以理解为:在执⾏深度测试前将⽴⽅体的深度值做⼀些细微的增加,于是就能将重叠的2个图形深度值之前有所区分。
//启⽤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
2、指定偏移量
- 通过glPolygonOffset 来指定.glPolygonOffset 需要2个参数: factor , units
- 每个Fragment 的深度值都会增加如下所示的偏移量:
Offset = ( m * factor ) + ( r * units);
m : 多边形的深度的斜率的最⼤值,理解⼀个多边形越是与近裁剪⾯平⾏,m 就越接近于0.
r : 能产⽣于窗⼝坐标系的深度值中可分辨的差异最⼩值.r 是由具体是由具体OpenGL 平台指定的⼀个常量. - ⼀个⼤于0的Offset 会把模型推到离你(摄像机)更远的位置,相应的⼀个⼩于0的Offset 会把模型拉近
- ⼀般⽽⾔,只需要将-1.0 和 -1 这样简单赋值给glPolygonOffset 基本可以满⾜需求
void glPolygonOffset(Glfloat factor,Glfloat units);
应⽤到⽚段上总偏移计算⽅程式:
Depth Offset = (DZ * factor) + (r * units);
DZ:深度值(Z值)
r:使得深度缓冲区产⽣变化的最⼩值
负值,将使得z值距离我们更近,⽽正值,将使得z值距离我们更远
一般来说,(-1,-1)就行
glPolygonOffset(-1,-1);
3、关闭多边形偏移 Polygon Offset
glDisable(GL_POLYGON_OFFSET_FILL)
三、颜色混合
OpenGL 渲染时会把每个像素点的颜⾊值存在颜⾊缓冲区中,每个像素点的深度值也是存在深度缓冲区中。当深度缓冲区被关闭时,新的颜⾊将简单的覆盖原来颜⾊缓存区存在的颜⾊值,当深度缓冲区再次打开时,新的颜⾊⽚段只是当它们⽐原来的值更接近邻近的裁剪平⾯才会替换原来的颜⾊⽚段。
如果在开启深度测试后,2个重叠的图层中,有⼀个图层是半透明的,有⼀个图层是⾮半透明的,OpenGL 需要怎么处理?
此时就不能进⾏单纯的⽐较深度值,然后进⾏覆盖。 ⽽是需要将2个图层的颜⾊进⾏混合。
注意:针对有两个图层叠加,一个半透明,一个不透明。
- 如果半透明图层在非透明图层的上层,进行颜色混合
- 如果半透明图层在非透明图层的下层,不需要考虑
1、混合开关
- 如果单纯讲两个图层进行颜色混合,在固定着色器和可编程着色器下,都可以直接打开开关进行。
//开启,
glEnable(GL_BlEND);
//使用着色器
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
//绘制
squareBatch.Draw();
//关闭
glDisable(GL_BlEND);
2、混合开关+颜色混合方程式
//开启,
glEnable(GL_BlEND);
//开启组合函数 计算混合颜色因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//使用着色器
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
//绘制
squareBatch.Draw();
//关闭
glDisable(GL_BlEND);
- 如果只是给了2个颜色进行混合,就需要用到颜色混合方程式
默认方程式:Cf = (Cs * S) + (Cd * D)
Cf :最终计算参数的颜⾊
Cs :源颜⾊ (作为当前渲染命令结果进⼊颜⾊缓存区的颜⾊值 [例如,现⼥友])
Cd :⽬标颜⾊ (已经存储在颜⾊缓存区的颜⾊值 [例如,前⼥友])
S:源混合因⼦
D:⽬标混合因⼦
这里我们只能指定 S 和 D
设置混合因⼦,需要⽤到glBlendFun函数
glBlendFunc(GLenum S,GLenum D);
S:源混合因⼦
D:⽬标混合因⼦
- 表中R、G、B、A 分别代表 红、绿、蓝、alpha。
表中下标S、D,分别代表源、⽬标
表中C 代表常量颜⾊(默认⿊⾊)
我们在这里举个例子
利用常见组合:glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
如果颜⾊缓存区已经有⼀种颜⾊Cd:红⾊(1.0f,0.0f,0.0f,0.0f),如果在这上⾯⽤⼀种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、拓展
a、设置组合方程式
选择混合⽅程式的函数:
glbBlendEquation(GLenum mode);
b、设置混合因⼦
除了能使⽤ glBlendFunc 来设置混合因⼦,还可以有更灵活的选择
void glBlendFuncSeparate(GLenum strRGB,GLenum dstRGB ,GLenum strAlpha,GLenum dstAlpha);
strRGB: 源颜⾊的混合因⼦
dstRGB: ⽬标颜⾊的混合因⼦
strAlpha: 源颜⾊的Alpha因⼦
dstAlpha: ⽬标颜⾊的Alpha因⼦
- glBlendFunc 指定 源和⽬标 RGBA值的混合函数;但是glBlendFuncSeparate函数则允许为RGB 和Alpha 成分单独指定混合函数。
- 在混合因⼦表中,
GL_CONSTANT_COLOR、
GL_ONE_MINUS_CONSTANT_COLOR、
GL_CONSTANT_ALPHA、
GL_ONE_MINUS_CONSTANT
的值允许混合⽅程式中引⼊⼀个常量混合颜⾊。
c、设置常量混合颜⾊
默认初始化为⿊⾊(0.0f,0.0f,0.0f,1.0f),但是还是可以修改这个常量混合颜⾊。
void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclampf alpha );
四、裁剪
裁剪:在OpenGL 中提⾼渲染的⼀种⽅式,只刷新屏幕上发⽣变化的部分。OpenGL 允许将要进⾏渲染的窗⼝只去指定⼀个裁剪框。
基本原理:⽤于渲染时限制绘制区域,通过此技术可以在屏幕(帧缓冲)指定⼀个矩形区域。启⽤剪裁测试之后,不在此矩形区域内的⽚元被丢弃,只有在此矩形区域内的⽚元才有可能进⼊帧缓冲。因此实际达到的效果就是在屏幕上开辟了⼀个⼩窗⼝,可以再其中进⾏指定内容的绘制。
//1、设置裁剪区域颜色
glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
//2、设置裁剪区域大小
glScissor(50, 50, 200, 200);
//3、开启裁剪测试
glEnable(GL_SCISSOR_TEST);
//4、开启清屏,执行裁剪
glClear(GL_COLOR_BUFFER_BIT);
//5、关闭裁剪测试
glDisable(GL_SCISSOR_TEST);