OpenGL-深度测试和颜色混合
在上一篇文章:使用OpenGL来画个甜甜圈中,我们看到在正背面剔除过后,是能够消除位于对象背面的图形,但是对于重叠的图像,依然还是有错误的渲染情况出现:
而要解决这个问题,就要用到我们的下一个知识点,深度测试。
1.什么是深度测试?
深度:其实就是该像素点在3D世界中距离摄像机的距离,z值
我们之前提到过油画算法,我们只要先简单的绘制背景,再在上面绘制较近的对象,这样做可能只用在画布上进行次数不多的绘制,但对于图形硬件来说,油画算法无法解决重叠的图层,而且在绘制有阻挡的图形的时候,会浪费很多的性能。
在不使用深度测试的时候,如果我们绘制一个有重叠的图形,先绘制距离较近的物体,再绘制距离较远的物体,则距离远的位图因为后绘制
深度测试:深度测试的概念很简单,在绘制一个像素时,将Z值(它到观察者的距离)分配给它,然后,当另外一个像素需要在屏幕上的同样位置进行绘制时,新像素的Z值将与已经存储的像素的Z值进行比较,如果新像素的Z值比较大,那么它距离观察者就更近,所以原来的像素就会被新的像素覆盖。
深度测试的启用也很简单:
glEnable(GL_DEPTH_TEST);
使用深度测试后,距离观察者较远的位图的深度值和颜色值会被丢弃,我们完整的甜甜圈,就绘制成功了:
深度值的范围是0-1,(OpenGL ES里面,想要开启深度测试,同样是调用glEnable),我们可以通过glDepthFunc(GLenum func)来修改深度测试的测试规则,我们使用glut设置窗口的时候,应该请求一个深度缓冲区。
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
为什么需要深度缓冲区:没有深度缓冲区,启用深度测试的命令将被忽略。
只用深度测试,不用背面剔除也可以解决甜甜圈问题:不管有多少图层,只显示你能看到的,即使两个图层重叠了,背面的颜色已经从深度缓冲区里丢弃了
2.深度测试并不是万能的
深度测试的风险:Z-fighting(Z冲突、闪烁)问题
由于深度缓冲区的精度是有限的,在深度相差非常小的情况下,可能会出现判断错误(深度测试结果不可预测)的情况,就会出现本应该显示在后面,结果显示到前面去了的情况
3.解决Z-fighting问题:
在两个图层之间加一个微妙的间隔。手动修改深度值的方法并不可取,OpenGL为我们提供了一个解决方案:多边形偏移
启用方式,glEnable(GL_Polygon_OFFSET_FILL),需要两个参数
Offset = (m * factor) + (r * units),好吧,这个公式并不需要记,factor和units一般都设置为-1,-1,就可以满足效果
如果想从根源上避免:
a.不要让两个物体靠的太近;
b.近裁剪平面设置的离观察者远一些
c.使用更高精度位数的深度缓冲区(所以现代设备很少出现Z-fighting问题)
4.多边形模式
多边形(包括三角形)不一定是实心的,默认情况下,多边形是作为实心图形进行绘制,但我们可以通过调整多边形模式,让多边形绘制为只有点或轮廓。此外,我们也可以在多边形的两面分别应用这个多边形模式,函数glPolygonMode允许将多边形渲染成实体、轮廓或者只有点:
glPolygonMode (GLenum face, GLenum mode);
多边形模式:
- 实体(GL_FULL)
- 轮廓(GL_LINE)
- 点(GL_POINT)
多边形模式应用面:
- 正面(GL_FRONT)
- 背面(GL_BACK)
- 双面(GL_FRONT_AND_BACK)
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
5.颜色混合
通常情况下OpenGL渲染时会把颜色值放在颜色缓冲区里,任何绘制操作都是完全覆盖原来的颜色值,比如我们在一个红色图形前面画一个蓝色图形,在重叠的部分,蓝色覆盖红色。但如果我们想在重叠区显示重叠颜色(比如红蓝混合色)怎么办?这时就需要使用到 OpenGL 的混合功能。
glEnable(GL_BLEND);
我们先介绍一下新输入颜色值和和已经存在的颜色值的正式术语:
目标颜色:已经存储在颜色缓冲区中的颜色
源颜色:将要加入进行混合的颜色
混合方程式:目标颜色和源颜色的组合方式,用来生成混合后的颜色
默认情况下,混合方程式如下:
Cf = (Cs * S) + (Cd * D)
其中Cf是最终计算产生的颜色,Cs是源颜色,Cd是目标颜色,S和D分别是源和目标混合因子
固定着色器/可编程着色器->使用开关的方式->颜色混合(单纯的两个图层重叠的时候混合)
可编程着色器里的片元着色器,如果需要图片的原始颜色上添加一层薄薄的绿色,需要进行颜色混合方程式的计算(套用公式)
1 // 我们通过控制 S 和 D 混合因子控制混合方程式输出,S 和 D 都是枚举值 2 void glBlendFunc(CLenum S, GLenum D);
下图列出了混合函数可以使用的值:
这个混合因子表并不需要大家记忆,我们只用其中一种情况的计算过程来进行类推即可:
1 /* 2 Rs/Gs/Bs/As - 源颜色 RGBA 各个通道的混合因子 3 Rd/Gd/Bd/Ad - 目标颜色 RGBA 各个通道的混合因子 4 Rc/Gc/Bc/Ac - 常量颜色 RGBA 各个通道的混合因子 5 Cs = 源颜色 = { 0.0f, 0.0f, 1.0f, 0.6f } 6 Cd = 目标颜色 = { 1.0f, 0.0f, 0.0f, 1.0f } 7 As = 源颜色 alpha 值 = 0.6f 8 Ad = 目标颜色 alpha 值 = 1.0f 9 S = 源颜色混合因子 = GL_SRC_ALPHA = As = 0.6f 10 D = 目标颜色混合因子 = GL_ONE_MINUS_SRC_COLOR = 1.0f - As = 0.4f 11 Cf = 最终产生颜色 = Cs * 0.6f + Cd * 0.4f = { 12 0.0f * 0.6f + 1.0f * 0.4f, 13 0.0f * 0.6f + 0.0f * 0.4f, 14 1.0f * 0.6f + 0.0f * 0.4f, 15 0.6f * 0.6f + 1.0f * 0.4f 16 } = { 0.4f, 0.6f, 0.0f, 0.76f } 17 */ 18 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
最终结果就是原来的红色(目标颜色)和后来的蓝色(源颜色)进行缩放后的组合,源颜色的alpha值越高,添加的源颜色成分就越多,目标颜色保留的成分就越少。
但是当我们进行了颜色混合后,最终目标颜色的 Alpha 同时也从0.6变成了0.76,这时我们就需要另外一个函数
1 //允许分别为 RGB 通道和 Alpha 通道设置混合因子(OpenGL 2.0 开始支持) 2 void glBlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
实际上,我们可以从5个不同的混合方程式中进行选择:
6.抗锯齿
OpenGL 混合功能的另一种用途是抗锯齿。在绝大多数情况下,一个独立的渲染片段会映射到计算机上的一个像素。而这些图形的边缘会出现一些吸引眼睛的注意力而让人感觉图形不自然的像素点,称为锯齿。
为了消除这些锯齿,达到一个平滑的效果,OpenGL 利用混合功能,把像素的目标颜色和周围像素的颜色进行混合。
抗锯齿功能开启条件:
1.开启混合功能
2.指定混合因子(
注意:如果你修改了混合方程式,当你使用混合抗锯齿功能时,请一定要改为默认混合方程式
3.开启对点\线\多边形的抗锯齿功能(
glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE_SMOOTH);
glEnable(GL_POLYGON_SMOOTH);
)
4.最后别忘了
关闭抗锯齿功能(
glDisable
)
这样,锯齿就消失了:
OpenGL还提供了一个新的特性——利用多重采样来实现抗锯齿:
glEnable(GLUT_MULTISAMPLE);
//绘制
moonBatch.Draw();
//绘制完成,则关闭
glDisable(GLUT_MULTISAMPLE);
7.小结:
通过这次的学习,我们了解到正面和背面环绕与表面剔除是大量图形渲染算法的重要组成部分,也是性能优化工作的重要组成部分;大多数3D场景,我们都需要深度测试来加速性能,有一些时候我们需要增加一个偏移来“欺骗”它,以及使用目标颜色(前女友)和源颜色(现女友)的混合功能,最后,我们还了解了使图形质量大大提高的抗锯齿功能。就此,我们对一个OpenGL图形的基本渲染流程,如何运用着色器进行渲染,怎么将顶点和其他属性加入图元批次,以及使用正确的着色器和uniform值对他们进行渲染,有了一个基本的了解。在这之后,我们将运用纹理技术,真正让我们的对象活跃起来。