一、渲染中可能会出现的问题
如果我们绘制一个由很多个三角形组成的实体对象,那么第一个绘制的三角形可能会被后面绘制的三角形覆盖。如下图这个像游泳圈似的模型,其中一些三角形在游泳圈的背面,另一些在正面,正常我们应该是看不到背面的(不考虑透明几何体的特殊情况)。这样的话,三角形绘制的顺序可能会一团糟,就变成了下图的样子:
image1.png
二、解决方法
1. 油画法(painters algorithm):
对这些三角形排序,先渲染较远的三角形,再在它们上方渲染较近的三角形。但这种方法在图形处理中效率很低,必须在任何发生重叠的地方对每个像素进行两次写操作,速度会变慢。并且对独立的三角形排序的开销会过高。所以一般不推荐使用。
2. 正面&背面剔除:
对正面和背面三角形进行区分的原因之一就是为了进行剔除。背面剔除能极大提高性能,避免上图出现的问题。它很高效,在渲染的图元装配阶段就整体抛弃了一些三角形。
//开启表面剔除(默认背面剔除)
void glEnable(GL_CULL_FACE);
//关闭表面剔除(默认背面剔除)
void glDisable(GL_CULL_FACE);
//⽤户选择剔除那个面(正⾯/背⾯)
//mode参数为: GL_FRONT,GL_BACK,GL_FRONT_AND_BACK ,默认GL_BACK
void glCullFace(GLenum mode);
//这样要消除不透明物体的内部几何图形就需要两行代码:
glCullFace(GL_BACK);
glEnable(GL_CULL_FACE);
在某些情况下,剔除实体几何体的正面也很有必要,比如要显示图形内部渲染的时候。在渲染透明对象时(下面马上就会讲到混合),我们经常会对一个对象进行两次渲染,第一次会开启透明并剔除正面,第二次则消除背面。这样就在渲染正面之前渲染了背面,这也是渲染透明物体的需要。但是在开启背面剔除后,会发现上面的游泳圈模型还是显示的有问题,如图:
image2.png
这是因为没有开启深度测试。
3. 深度测试:
深度:
深度就是在openGL坐标系中,像素点的 Z 坐标距离观察者的距离。观察者可能放在坐标系的任何位置,那么,就不能简单的说 Z 数值越大或越小,就是越靠近观察者。
如果观察者在Z轴的正方向,Z 值大的靠近观察者,如果是在Z轴的反方向,则 Z 值小的更靠近观察者。
深度缓冲区(DepthBuffer)
深度缓冲区原理就是把一个距离观察平面(近裁剪面)的深度值(或距离)与窗口中的每个像素相关联。
首先,使用glClear(GL_DEPTH_BUFFER_BIT),把所有像素的深度值设置为最大值。
如果启用了深度缓冲区,在绘制每个像素之前,OpenGL会把它的深度值和已经存储在这个像素的深度值进行比较。如果,新像素深度值 < 原先像素深度值,则新像素值会取代原先的;反之,新像素值被遮挡,它的颜色值和深度将被丢弃。
这个比较、丢弃的过程就叫做 深度测试,深度测试是另一种高效消除隐藏面的技术。
//申请一个颜色缓冲区和一个深度缓冲区:
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//要启用深度测试,只需调用
glEnable(GL_DEPTH_TEST);
//关闭深度测试
glDisable(GL_DEPTH_TEST);
//如果没有深度缓冲区,那么启动深度测试的命令将被忽略。在绘制场景前,清除颜色缓冲区和深度缓冲区:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
开启了深度测试后,我们终于得到了一个我们想要的游泳圈模型:
image3.png
清除深度缓冲区的默认值是1.0,表示最大的深度值,深度值的范围在[0,1]之间。
用户通过glDepthFunc(GLenum func)函数指定深度测试的规则,这个函数包括一个参数,如下表:
image4.png
结论:使⽤正⾯/背⾯剔除法和深度测试法来解决渲染效率问题.
z-fighting(z冲突、闪烁)问题:
image5.png因为开启深度测试后,OpenGL 就不会再去绘制模型被遮挡的部分. 这样实现的显示更加真实.但是 由于深度缓冲区精度的限制对于深度相差非常⼩的情况下.(例如在同一平面上进⾏2次绘制),OpenGL 就可能出现不能正确判断两者的深度值,会导致深度测试的结果不可预测.显示出来的现象时交错闪烁前面2个画⾯,交错出现.
解决方法
让深度值之间产生间隔.如果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;
第二步:指定偏移量
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)
避免深度值相同造成的z-fighting冲突问题的几种做法:
1.不要将两个物体靠的太近,避免渲染时三角形叠在一起。这种方式要求对场景中物体插入一个少量的偏移,那么就可能避免ZFighting现象。例如上面的立方体和平面问题中,将平面下移0.001f就可以解决这个问题。当然手动去插⼊这个小的偏移是要付出代价的。
2.尽可能将近裁剪面设置得离观察者远一些。上面我们看到,在近裁剪平面附近,深度的精确度是很⾼的,因此尽可能让近裁剪⾯远一些的话,会使整个裁剪范围内的精确度变高一些。但是这种方式会使 离观察者较近的物体被裁减掉,因此需要调试好裁剪面参数。
3.使⽤更高位数的深度缓冲区,通常使⽤的深度缓冲区是24位的,现在有一些硬件使⽤32位的缓冲区,使精确度得到提⾼
三、裁剪
另一种提高渲染性能的方法是只刷新屏幕上发生变化的部分。我们可能还需要将 OpengGL 渲染限制在窗口中一个较小的矩形区域(剪裁框)中。裁剪测试是片元可见性判断的第一个附加测试。
//默认情况下,剪裁框与窗口同样大小,并且不会进行裁剪测试。
glEnable(GL_SCISSOR_TEST);// 开启裁剪测试
glDisable(GL_SCISSOR_TEST);// 关闭裁剪测试
剪裁框可以通过下面的函数设置位置与大小:
void glScissor(Glint x,Glint y,GLSize width,GLSize height);
参数 x,y:指定裁剪框左下⻆的点位置(x,y);
参数 width , height:指定裁剪的宽和高。
下面这个程序利用剪裁测试绘制一组重叠的彩色矩形,它对颜色缓冲区进行了3次清除操作,每次清除之前都指定了一个比上一个较小的剪裁框。
//设置清屏颜色为蓝色
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();
效果图:
image6.png
网友评论