OpenGL正面剔除,深度测试,混合
问题抛出
//4.创建一个甜甜圈
//void gltMakeTorus(GLTriangleBatch& torusBatch, GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor);
//参数1:GLTriangleBatch 容器帮助类
//参数2:外边缘半径
//参数3:内边缘半径
//参数4、5:主半径和从半径的细分单元数量
gltMakeTorus(torusBatch, 1.0f, 0.3f, 52, 26);
通过该API绘制一个甜甜圈,如图
甜甜圈
当我们进行旋转的时候会出现以下情况
旋转后
为什么会出现这个原因呢?
任何一个3D图形都有两个面,一个是阳面(光照面),一个是阴面(背光面),在旋转时本该显示阳面的但是部分却显示了阴面,
原因:因为旋转过程中,OpenGL不知道该显示哪个面,本不能够看到的面,却显示了出来,导致了这种原因
正面剔除
背景:
一个3D图形,从任何一个方向看,最多可以看到三面,一个立方体从任何方向看都最多只能看到3面
那么看不到的面就没有必要在去进行绘制,如果我们把这部分数据丢弃掉,那么OpenGL渲染的性能即可提高超过50%
那么怎么知道哪个是阳面哪个是阴面呢?
- 按照逆时针顶点链接顺序的三角形为阳面
- 按照顺时针顶点链接的为三角形的阴面
-
当观察者在右侧时,则右边的三角形方向为逆时针方向则为正面,而左侧三角形为顺时针则为阴面
-
反之观察者在左侧时,左侧三角形为逆时针方向为正面,右侧为阴面
-
注意 : 正面和背面是由三角形顶点定义顺序和观察者方向决定的,观察者角度改变时,正背面也会跟着改变
但是也有弊端,比如这个甜甜圈旋转到一下位置时,会有两个正面和背面,OpenGL无法正确显示正面
开启表⾯面剔除(默认背⾯面剔除)
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
例例如,剔除正⾯面实现(1)
glCullFace(GL_BACK);
glFrontFace(GL_CW);
例例如,剔除正⾯面实现(2)
glCullFace(GL_FRONT);
深度测试
深度
深度其实就是该像素在3D世界中距离相机的距离 Z值
深度缓冲区
一块内存区域,专门存储着每个像素点的深度值,深度值越大则离相机就越远
在不同深度测试的时候,如果我们先绘制一个距离比较近的物体,在绘制距离比较远的物体,则距离远的位图因为后绘制,会把距离近的物体覆盖掉,有了深度缓冲区后,绘制物体的顺序就不那么重要,实际上,只要存在深度缓冲区,OpenGL就会把像素的深度值写入到缓冲区中,除非调用glDepthMask(GL_FALSE)来禁止写入
深度测试
深度缓冲区和颜色缓冲区是对应的,颜色缓冲区存储像素的颜色信息,深度缓冲区存储像素的深度信息,在决定是否绘制一个物体表面时,首先要将表面对应的像素的深度值与当前深度缓冲区中的值进行比较,如果大于深度缓冲区的值,则丢弃这部分数据,否则利用这个像素对应的深度值和颜色值,分别更新深度缓冲区和颜色缓冲区,这个过程称为 "深度测试"
-
深度缓冲区,⼀一般由窗⼝口管理理系统,GLFW创建.深度值⼀一般由16位,24位,32位值表示. 通常是24位.位数越⾼高,深度精确度更更好.
-
清除深度缓冲区默认值为1.0,表示最⼤大的深度值,深度值的范围为(0,1)之间. 值越⼩小表示越靠近观察者,值越⼤大表示 越远离观察者
- 开启深度测试
开启深度测试
glEnable(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);
函数 | 说明 |
---|---|
GL_ALWAYS | 总是通过测试 |
GL_NEVER | 总是不通过测试 |
GL_LESS | 在当前深度值 < 存储的深度值时通过 |
GL_GREATER | 在当前深度值 > 存储的深度值时通过 |
GL_EQUAL | 在当前深度值 == 存储的深度值时通过 |
GL_LEQUAL | 在当前深度值 <= 存储的深度值时通过 |
GL_GEQUAL | 在当前深度值 >= 存储的深度值时通过 |
GL_NOTEQUAL | 在当前深度值 != 存储的深度值时通过 |
- 打开/阻断 深度缓冲区写入
void glDepthMask(GLBool value);
value : GL_TURE 开启深度缓冲区写⼊入; GL_FALSE 关闭深度缓冲区写⼊入
ZFighting(Z冲突)闪烁问题
为什什么会出现 ZFighting 闪烁问题
因为开启深度测试后,OpenGL 就不不会再去绘制模型被遮挡的部分. 这样实现的显示更更加真实.但是 由于深度缓冲区精度的限制对于深度相差⾮非常⼩小的情况下.(例例如在同⼀一平⾯面上进⾏行行2次 制),OpenGL 就可能出现不不能正确判断两者的深度值,会导致深度测试的结果不不可预测.显示出来的 现象时交错闪烁.的前⾯面2个画⾯面,交错出现.
image.png解决办法
- 启⽤用 Polygon Offset ⽅方式解决
让深度值之间产⽣生间隔.如果2个图形之间有间隔,是不不是意味着就不不会产⽣生⼲干涉.可以理理 解为在执⾏行行深度测试前将⽴立⽅方体的深度值做⼀一些细微的增加.于是就能将叠的2个图形深度值之 前有所区分.
//启⽤用Polygon Offset ⽅方式
//参数列列表: GL_POLYGON_OFFSET_POINT GL_POLYGON_OFFSET_LINE GL_POLYGON_OFFSET_FILL
//对应光栅化模式: GL_POINT 对应光栅化模式: GL_LINE
//对应光栅化模式: GL_FILL
glEnable(GL_POLYGON_OFFSET_FILL)
- 指定偏移量
通过glPolygonOffset 来指定.glPolygonOffset 需要2个参数: factor , units
void glPolygonOffset(Glfloat factor,Glfloat units);
//应⽤用到⽚片段上总偏移计算⽅方程式:
//Depth Offset = (DZ * factor) + (r * units); DZ:深度值(Z值)
//r:使得深度缓冲区产⽣生变化的最⼩小值
//负值,将使得z值距离我们更更近,⽽而正值,将使得z值距离我们更更远, 对于上节课的案例例,我们设置factor和units设置为-1,-1
- 关闭Polygon Offset
glDisable(GL_POLYGON_OFFSET_FILL)
问题预防:
- 不不要将两个物体靠的太近,避免渲染时三⻆角形叠在⼀一起.这种⽅方式要求对场景中物体插⼊入⼀一个少量量的 偏移,那么就可能避免ZFighting现象。例例如上⾯面的⽴立⽅方体和平⾯面问题中,将平⾯面下移0.001f就可以解 决这个问题。当然⼿手动去插⼊入这个⼩小的偏移是要付出代价的。
- 如果观察者离近裁剪平⾯很近,那么深度测试对精确度要求很⾼。因此,可以适当推远近裁剪平⾯的位置来避免这个问题,但是可能导致离观察者较近的物体被裁减掉,使用时需要小心。
- 使⽤用更更⾼高位数的深度缓冲区,通常使⽤用的深度缓冲区是24位的,现在有⼀一些硬件使⽤用使⽤用32位的缓冲 区,使精确度得到提⾼高
混合
OpenGL 渲染时会把颜⾊色值存在颜⾊色缓存区中,每个⽚片段的深度值也是放在深度缓冲区。当深度 缓冲区被关闭时,新的颜⾊色将简单的覆盖原来颜⾊色缓存区存在的颜⾊色值,当深度缓冲区再次打开时,新 的颜⾊色⽚片段只是当它们⽐比原来的值更更接近邻近的裁剪平⾯面才会替换原来的颜⾊色⽚片段。
开启混合
//开启混合
gl_Enable(GL_BIEND);
混合方程式
⽬标颜色:已经存储在颜⾊色缓存区的颜⾊色值
源颜色:作为当前渲染命令结果进⼊入颜⾊色缓存区的颜⾊色值
当混合功能被启动时,源颜⾊色和⽬目标颜⾊色的组合⽅方式是混合⽅方程式控制的。在默认情况 下,混合⽅方程式如下所示
Cf = (Cs * S) + (Cd * D)
//Cf :最终计算参数的颜⾊色
//Cs : 源颜⾊色
//Cd :⽬目标颜⾊色
//S:源混合因⼦子
//D:⽬目标混合因⼦子
设置混合因子
//S:源混合因⼦
//D:⽬标混合因子
glBlendFunc(GLenum S, GLenum D);
函数 | RGB混合因子 | Alpha混合因子 |
---|---|---|
GL_ZERO | (0, 0, 0) | 0 |
GL_ONE | (1, 1, 1) | 1 |
GL_SRC_COLOR | (Rs, Gs, Bs) | As |
GL_ONE_MINUS_SCR_COLOR | (1, 1, 1) - (Rs, Gs, Bs) | 1 - As |
GL_DST_COLOR | (Rd, Gd, Bd) | Ad |
GL_ONE_MINUS_DST_COLOR | (1, 1, 1) - (Rd, Gd, Bd) | 1 - Ad |
GL_SRC_ALPHA | (As, As, As) | As |
GL_ONE_MINUS_SCR_ALPHA | (1, 1, 1) - (As, As, As) | 1- As |
GL_DST_ALPHA | (Ad, Ad, Ad) | Ad |
GL_ONE_MINUS_DST_ALPHA | (1, 1, 1) - (Ad, Ad, Ad) | 1- Ad |
GL_CONSTANT_COLOR | (Rc, Gc, Bc) | Ac |
GL_ONE_MINUS_CONSTANT_ALPHA | (1, 1, 1) - (Ac, Ac, Ac) | 1- Ac |
GL_CONSTANT_ALPHA | (Ac, Ac, Ac) | Ac |
GL_ONE_MINUS_CONSTANT_ALPHA | (1, 1, 1) - (Ac, Ac, Ac) | 1- Ac |
GL_SRC_ALPHA_SATURATE | (f, f, f)* f = min(As, 1 - Ad) | 1 |
R、G、B、A 分别代表 红、绿、蓝、Alpha
下标S、D,分别代表源、⽬标
C 代表常量颜⾊(默认⿊色)
改变混合方程式
OpenGL有5个不同的方程式进行选择。
glbBlendEquation(GLenum mode);
模式 | 函数 |
---|---|
GL_FUNC_ADD | Cf = (Cs * S) + (Cd * D) |
GL_FUNC_SUBTRACT | Cf = (Cs * S) - (Cd * D) |
GL_FUNC_REVERSE_SUBTRACT | Cf = (Cd * D) - (Cs * S) |
GL_MIN | Cf = min(Cs, Cd) |
GL_MAX | Cf = max(Cs, Cd) |
glBlendFuncSeparate函数
除了能使⽤OpenGL内置的混合因⼦,还可以有更灵活的选择。
//strRGB: 源颜色的混合因⼦
//dstRGB: 目标颜⾊的混合因⼦
//strAlpha: 源颜⾊的Alpha因⼦
//dstAlpha: 目标颜⾊的Alpha因⼦
void glBlendFuncSeparate(GLenum strRGB, GLenum dstRGB , GLenum strAlpha, GLenum dstAlpha);