首先我们来欣赏一张虽然残缺但是美丽的甜甜圈:
残缺美
我们在渲染这张图片时是有无数个三角形组成的,如果我们绘制的是一个2D图形试想一下是不会出现这种情况的。但是我们绘制的是3D图形,当我们在一个一个的渲染这无数个三角形的时候难免会存在覆盖的现象,一旦被覆盖,在屏幕上就相当于消失了。所以当我们旋转该甜甜圈的时候就会存在有黑色的部位。
下面我们就来讲解一下由该问题引来的相关概念,以及解决方式。
隐藏面消除
在绘制3D场景的时候,我们需要决定哪些部分是对观察者可见的,或者哪些部分是对观察者不可⻅的。对于不可见的部分,应该及早丢弃。这种情况叫做”隐藏面消除”(Hidden surface elimination)。
例如1、在⼀个不透明的墙壁后,就不应该渲染。
例如2、一个正方形,无论你从哪个角度去观察,最多只能看到三个面,至少有一个面是看不到的,看不到的那个面就不应该渲染,提高性能。
那么问题就来了,我们如何知道某个面在观察者视野中不会出现?
- 这时OpenGL的强大作用就体现出来了,他可以检查所有正面朝向观察者的⾯,并渲染它们。从而丢弃背面朝向的⾯。 这样可以节约片元着⾊器的性能。渲染的性能即可提高超过50%。解决方案:正背面剔除(Face Culling)法。
正背面剔除(Face Culling)
- 如何告诉OpenGL哪个是正面,哪个是背面?
答案:分析顶点数据的顺序。
分析顶点顺序:
OpenGL中默认顶点按照逆时针顺序链接的面为正面,顶点顺时针连接的面为背面。
当然也可以修改:
glFrontFace(GL_CW);
GL_CW
:告诉OpenGL 顺时针环绕的多边形为正⾯。GL_CCW
:告诉OpenGL 逆时针环绕的多边形为正面。
立方体中的顶点顺序
- 首先靠近眼睛的三角形顶点的连接顺序:A->B->C。
为逆时针方向,所以默认是正面。 - 再看远离眼睛的三角形顶点的连接顺序:A->B->C。
为顺时针方向,所以默认是背面。 - 当你的眼睛挪到左边去的时候,你会发现左边的三角形也是按照A->B->C的顺序逆时针链接的。这时左边三角形就变成了正面。
理解:
正面和背面是相对而言并不是绝对的。是由顶点的绘制顺序和观察者共同决定的。
正背面剔除相关代码:
-
开启表面剔除(默认背面剔除)
void glEnable(GL_CULL_FACE);
-
关闭表⾯剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);
-
⽤户选择剔除哪个面(正面/背面)
void glCullFace(GLenum mode);
mode
参数为:GL_FRONT
,GL_BACK
,GL_FRONT_AND_BACK
,默认GL_BACK
-
用户指定哪个为正面(一般情况下不需要改这个配置)
void glFrontFace(GLenum mode);
mode
参数为:GL_CW
、GL_CCW
。默认值:GL_CCW
(逆时针为正面)。
我们开启正背面剔除后再看下残缺而又美丽的甜甜圈有什么变化:
我们发现旋转到这个位置时黑色部位没有了,但是是否已经完美了?我们继续旋转,发现如下效果:
我们继续旋转时发现出现了新的问题,然而这种问题又是怎么出现的呢,这就引出了一个新的概念 深度测试 。下面我们来了解一下什么是深度测试。
深度测试
首先,我们需要知道深度的含义:
深度就是像素点在3D世界中距离摄像机的距离,Z值 。
-
深度缓冲区(
Depth-buffer
):
深度缓冲区就是一块内存区域,用来存储像素点(绘制在屏幕上的)的深度值。深度值越大,则离摄像机就越远。 -
深度缓冲区的必要性:
在不使用深度测试的时候,如果我们先绘制⼀个距离比较近的物体,再绘制距离较远的物体,则距离远的位图因为后绘制,会把距离近的物体覆盖掉。 有了深度缓冲区后,绘制物体的顺序就不那么重要了。 实际上,只要存在深度缓冲区,OpenGL 都会把像素的深度值写入到缓冲区中。 除非调用glDepthMask(GL_FALSE)
来禁⽌写入。 -
深度测试
深度缓冲区和颜色缓存区是对应的。颜色缓存区存储像素的颜色信息,而深度缓冲区存储像素的深度信息。在决定是否绘制⼀个物体表面时,首先要将表⾯面对应的 像素的深度值 与当前深度缓冲区中的值进行比较。 如果大于深度缓冲区中的值,则丢弃这部分。否则 利用这个像素对应的深度值和颜⾊值。分别更新深度缓冲区和颜色缓存区.。这个过程称为“深度测试”。 -
深度值计算
深度值⼀般由16位,24位或者32位值表示,通常是24位。位数越⾼的话,深度的精确度越好。深度值的范围在[0,1]之间,值越小表示越靠近观察者,值越大表示远离观察者。
深度缓冲主要是通过计算深度值来比较⼤小,在深度缓冲区中包含深度值介于0.0和1.0之间, 从观察者看到其内容与场景中的所有对象的 z 值进⾏了比较。这些视图空间中的 z 值可以在投影平头截体的近平面和远平面之间的任何值。我们因此需要一些⽅法来转换这些视图空间 z 值到 [0,1] 的范围内,下⾯的 (线性) 方程把 z 值转换为 0.0 和 1.0 之间的值 :
far和near是提供到投影矩阵设置可见视图截锥的远近值。
-
开启深度测试
glEnable(GL_DEPTH_TEST);
-
在绘制场景前,清除颜⾊缓存区,深度缓冲
glClearColor(0.0f,0.0f,0.0f,1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
-
清除深度缓冲区默认值为1.0,表示最⼤的深度值,深度值的范围为(0,1)之间。值越小表示越靠近观察者,值越⼤表示越远离观察者。
-
指定深度测试判断模式
void glDepthFunc(GLEnum mode);
GL_ALWAYS
:总是通过测试
GL_NEVER
:总是不通过测试
GL_LESS
:当前深度值 < 存储深度值 时通过
GL_EQUAL
:当前深度值 = 存储深度值 时通过
GL_LEQUAL
:当前深度值 <= 存储深度值 时通过
GL_GREATER
:当前深度值 > 存储深度值 时通过
GL_NOTEQUAL
:当前深度值 ≠ 存储深度值 时通过
GL_GEQUAL
:当前深度值 >= 存储深度值 时通过 -
打开/阻断 深度缓存区写入
void glDepthMask(GLBool value);
value :GL_TURE
开启深度缓冲区写入;GL_FALSE
关闭深度缓冲区写⼊。
完美!
下面我们来介绍一下我们玩手机或电脑经常遇到的一个问题:
ZFighting闪烁问题的原因:
因为开启深度测试后,OpenGL 就不会再去绘制模型被遮挡的部分。这样实现的显示更加真实。但是由于深度缓冲区精度的限制对于深度相差非常⼩的情况下。(例如在同一平⾯上进行2次绘制),OpenGL就可能出现不能正确判断两者的深度值,会导致深度测试的结果不可预测。显示出来的现象交错闪烁,前⾯2个画⾯,交错出现。
ZFighting闪烁问题解决方式
1、启⽤ glPolygonOffset t
,函数调节片段的深度值,使得深度值偏移而不产生重叠。
2、在第二次绘制时,插入一个少量的偏移。
ZFighting闪烁问题预防:
- 不要将两个物体靠的太近,避免渲染时三角形叠在⼀起。
- 尽可能将近裁剪面设置得离观察者远⼀些。
- 使⽤更高位数的深度缓冲区,通常使⽤的深度缓冲区是24位的,现在有⼀些硬件使用32位的缓冲区,使精确度得到提高。
裁剪
在OpenGL 中提高渲染的⼀种⽅式。只刷新屏幕上发⽣变化的部分。OpenGL 允许将要进行渲染的窗⼝只去指定⼀个裁剪框.
基本原理:用于渲染时限制绘制区域,通过此技术可以在屏幕(帧缓冲)指定⼀一个矩形区域。启⽤剪裁测试之后,不在此矩形区域内的⽚元被丢弃,只有在此矩形区域内的⽚元才有可能进⼊帧缓冲。因此,实际达到的效果就是在屏幕上开辟了一个⼩窗口,可以在其中进⾏指定内容的绘制。
- 1、开启裁剪测试
glEnable(GL_SCISSOR_TEST);
- 2、关闭裁剪测试
glDisable(GL_SCISSOR_TEST);
- 3、指定裁剪窗⼝
void glScissor(Glint x,Glint y,GLSize width,GLSize height);
x
、y
:指定裁剪框左下角位置;width
、height
:指定裁剪尺⼨
窗口、视口、裁剪区域
- 窗口: 就是显示界⾯面
- 视⼝: 就是窗⼝中⽤用显示图形的⼀一矩形区域,它可以和窗⼝等⼤,也可以⽐比窗口大或者⼩。只有绘制在视口区域中的图形才能被显示,如果图形有一部分超出了视⼝区域,那么⼀一部分是看不到的。过
glViewport()
函数设置。 - 裁剪区域(平行投影):就是视口矩形区域的最小最大
x
坐标(left,right
)和最小最⼤y
坐标 (bottom,top
),⽽不是窗口的最小最大x
坐标和y
坐标。通过glOrtho()
函数设置,这个函数还需指定最近最远z
坐标,形成一个立体的裁剪区域。
混合
OpenGL 渲染时会把颜色值存在颜色缓存区中,每个⽚段的深度值也是放在深度缓冲区。当深度缓冲区被关闭时,新的颜⾊将简单的覆盖原来颜色缓存区存在的颜色值,当深度缓冲区再次打开时,新的颜⾊片段只是当它们比原来的值更接近邻近的裁剪平面才会替换原来的颜色片段。
glEnable(GL_BlEND)
组合颜色
⽬标颜色:已经存储在颜⾊缓存区的颜色值。
源颜色:作为当前渲染命令结果进入颜色缓存区的颜色值
当混合功能被启动时,源颜色和⽬标颜色的组合方式是混合方程式控制的。在默认情况下,混合⽅程式如下所示:
Ci = (Cs * S) + (Cd * D)
Ci
:最终计算参数的颜⾊
Cs
: 源颜⾊
S
:源混合因子
Cd
:目标颜⾊
D
:目标混合因⼦
- 设置混合因子
设置混合因子,需要用到glBlendFun
函数
glBlendFunc(GLenum S,GLenum D);
S
和D
是枚举值
屏幕快照 2020-07-03 下午5.06.01.png
上面表格的颜色值都是用浮点数来表示的。举例说明:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
通过上面的调用来告诉OpenGL我们使用的混合因子。假设现在渲染命令传进来的源RGBA颜色值是(1.0f,0.0f,1.0f,1.0f)。而在颜色缓冲区相同的位置已经有目标颜色RGBA值是(0.0f, 1.0f, 0.0f, 1.0f)。
代入公式:Ci = (Cs * S) + (Cd * D)
- Cs=
(1.0f,0.0f,1.0f,0.6f)
,GL_SRC_ALPHA
参数说明源混合因子取源颜色的alpha值,所以S = 0.6f
。 - Cd=
(0.0f, 1.0f, 0.0f, 1.0f)
.GL_ONE_MINUS_SRC_ALPHA
说明了目标混合因子取1-As
,D = 1-0.6 = 0.4f;
所以代入计算得:
(Cs * S) = (0.6f, 0.0f, 0.6f, 0.36f)
(Cd * D) = (0.0f, 0.4f, 0.0f, 0.4f)
Ci = (Cs * S) + (Cd * D) = (0.6f, 0.4f, 0.6f, 0.76f)
得到的颜色是近似于洋紫色。
总结:
最终颜色是以目标颜⾊与源颜色进行组合。源颜色的alpha值越高,添加的源颜色成分越高,目标颜⾊所保留的成分就会越少。 混合函数经常用于实现在其他⼀些不透明的物体前⾯绘制一个透明物体的效果。
网友评论