上一篇我们分析了深度测试可以一次性解决隐藏面消除和深度的问题,但是它也带来了潜在风险——Z-fighting (Z冲突.闪烁)问题。
因为开启深度测试后,OpenGL不会再去绘制模型被遮挡的部分,这样实现的显示效果虽然更真实了,但是由于深度缓冲区精度的限制,在深度差值非常小的情况下,(例如在一个平面进行2次制),OpenGL就可能出现不能正确判断两者的深度值,导致深度测试的结果不可预测。显示出来的现象时交错闪烁的。

也就是说同一个位置上出现的图层,且深度值出现精确度很低到OpenGL认为是同一个平面时,引起了ZFighting现象。无法确定两个物体哪个在前,哪个在后。
多边形偏移(Polygon Offset)
那么我们要怎么解决这个问题呢?
既然是因为靠的太近,那么我们就在两个图层中间加入一点微小的间隙,增加深度差值,但是手动添加会不够精确而且很复杂,那么我们可以通过OpenGL启动多边形偏移Polygon Offset
。
可以理解为在深度测试前把立方体的深度值做一些细微的增加,于是两个图形深度值就较之前有了区分。
启动方式:
1.启⽤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 和 -1 这样简单赋值给glPolygonOffset 基本可以满⾜需求。
glPolygonOffset (GLfloat factor, GLfloat units)
,一般填-1,-1。
3.关闭多边形偏移
glDisable(GL_POLYGON_OFFSET_FILL)
如果要避免出现ZFighting闪烁问题,我们平常就要注意一下几点:
- 不要两个物体z值设得太接近,可以对物体间插入一个少量的偏移。一般设置0.001f就可以解决上图这种问题了。
- 把近裁剪面设置离摄像机远一点。也就是在离近裁剪面越近的地方,深度精度会很高,z值很小,因此只要离的远一点,整个裁剪范围精度就变高一些。但是这种方式会使离观察者较近的物体被裁剪掉,所以要调试好剪面参数。
- 使用位数更高的深度缓冲区,有一些硬件使用32/64位的缓冲区,精确度得到提高。
裁剪
在OpenGL中提高渲染的一种方式,就是指定部分要渲染的窗口(裁剪框),每次屏幕刷新只会渲染指定发生变化的部分。
基本原理:用于渲染时限制区域,通过此技术可以在屏幕(帧缓冲)指定一个矩阵区域。启用矩阵测试之后,不在此矩阵区域内的片元被丢弃了,只有在此区域的片元才会进去帧缓冲。因此效果就是在屏幕开辟了一个小窗口,可以在其中进行指定内容的绘制。
-
开启裁剪测试
GLEnabl(GL_SCISSOR_TEST);
-
关闭裁剪测试
glDisable(GL_SCISSOR_TEST)
-
指定裁剪窗口(x、y指定裁剪框左下角位置;width、height指定裁剪尺寸)
glScissor(Glint x, GLint y, GLSize width, GLSize height);
视口和窗口
-
窗口:就是显示界面
-
视口:就是窗口中用来显示图形的矩阵区域,可以和窗口等大,也可以比窗口小或大。只有绘制在里面的图形才能被显示,视口区域外的部分看不到。通过
glViewport()
设置。 -
裁剪区域(平行投影):就是将视口矩阵区域的最小最大x(left、right)、y坐标(bottom、top),而不是窗口的坐标。通过
glOrtho()
函数设置。这个函数需要指定最远z坐标,形成一个立体的裁剪区域。
示例在RenderScene中
void RenderScene(void)
{
//设置清屏颜色为蓝色
glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
//1.现在剪成小红色分区
//(1)设置裁剪区颜色为红色
glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
//(2)设置裁剪尺寸
glScissor(100, 100, 600, 400);
//(3)开启裁剪测试
glEnable(GL_SCISSOR_TEST);
//(4)开启清屏,执行裁剪
glClear(GL_COLOR_BUFFER_BIT);
// 2.裁剪一个绿色的小矩形
//(1).设置清屏颜色为绿色
glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
//(2).设置裁剪尺寸
glScissor(200, 200, 400, 200);
//(3).开始清屏执行裁剪
glClear(GL_COLOR_BUFFER_BIT);
//关闭裁剪测试
glDisable(GL_SCISSOR_TEST);
//强制执行缓存区
glutSwapBuffers();
}

颜色混合
OpenGL 渲染时会把颜⾊值存在颜⾊缓存区中,而每个⽚段的深度值也是放在深度缓冲区。当深度缓冲区被关闭时,新的颜⾊将简单的覆盖原来颜⾊缓存区存在的颜⾊值,当深度缓冲区再次打开时,新的颜⾊⽚段只是当它们⽐原来存储的值更接近邻近的裁剪平⾯才会替换原来的颜⾊⽚段.
当我们开启深度测试之后,如果两个重叠的图层中,一个是半透明图层,一个是非半透明的,那么此时不能单纯的比较它们的深度值,需要进行颜色混合。
颜色混合的方式:
1.使用开关的方式。就是对两个图层重叠进行混合。
2.在一个图层原图上进行颜色值混合。这个可以用于片元着色器的混合,需要颜色混合方程式的套用计算。
当混合功能开启时,源颜色(作为当前渲染命令结果进⼊颜⾊缓存区的颜⾊值)和目标颜色(已经存储在颜⾊缓存区的颜⾊值)的组合方式是混合方程式控制的。默认的混合方程式是
Cf = (Cs * s) + (Cd + D)
。
Cf:最终计算参数的颜色
Cs:源颜色
Cd:目标颜色
S:源混合因子
D:目标混合因子

可以通过
glBlendFunc(GLenum S,GLenum D)
设置混合因子。
我们比较常用的颜色混合组合是
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个函数调用
- 通过glEnable开启混合,开启的功能取决于参数,这里我们用
GL_BLEND
。 - 通过glBlendFunc,设置混合组合。
- 绘制完成后,需要关闭混合功能,通过
glDisable
关闭,也是关闭混合功能GL_BLEND
在渲染回调方法里开启混合功能,核心代码如下
//1.开启混合
glEnable(GL_BLEND);
//2.开启组合函数 计算混合颜色因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//3.使用着色器管理器
//*使用 单位着色器
//参数1:简单的使用默认笛卡尔坐标系(-1,1),所有片段都应用一种颜色。GLT_SHADER_IDENTITY
//参数2:着色器颜色
shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
//4.容器类开始绘制
squareBatch.Draw();
//5.关闭混合功能
glDisable(GL_BLEND);
我们来看看效果。

拓展:
1.此外。虽然默认混合方程式是Cf = (Cs * S) + (Cd * D)
,我们也可以从其他5种方程式中选择。
模式 | 函数 |
---|---|
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) |
通过选择混合⽅程式的函数选择:glbBlendEquation(GLenum mode)
。
- 常量混合颜⾊,默认初始化为⿊⾊(0.0f,0.0f,0.0f,0.0f),但是还是可以修改这个常量混合颜⾊。 通过
void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclam pf alpha )
修改。
网友评论