OpenGL渲染技巧

1,001 阅读6分钟

OpenGL渲染技巧

了解了OpenGL的渲染流程和常用API后,就可以简单的绘制出图形了。但是在绘制中可能会碰到些意想不到的问题。

在默认情况下,我们所渲染的每个点、线或三角形都会在屏幕上进行光栅化,并且会按照在组合图元批次时指定的顺序进行排列,这在某些情况下会产生问题。其中一个可能出现的问题是,如果我们绘制一个由很多个三角形组成的实体对象,那么第一个绘制的三角形可能会被后面绘制的三角形覆盖。

1.旋转中的圆环

图中,圆环上有些三角形在圆环的背面,而另一些则在圆环的正面。我们应该是看不到背面的。

多边形的正面和背面

OpenGL中最简单的实体多边形就是三角形,它只有3个边。光栅化硬件最欢迎三角形,而现在三角形已经是OpenGL中支持的唯一一种多边形了。每3个顶点定义一个新的三角形。下图是两个三角形,它们是用6个顶点进行绘制的,这6个顶点编号依次为V0到V5。

2.顺时针环绕

请注意连接顶点的线段上所标示的箭头。在绘制第一个三角形时,线条将按照从V0到V1,再到V2,最后回到V0的顺序来绘制一个闭合的三角形。这个路径是按照顶点被指定的顺序沿着顺时针方向的,这种方向特性也体现在了第二个三角形中。这种顺序与方向结合来指定顶点的方式称为环绕。图中右边的三角形就被称作是顺时针环绕的。如果我们将左边三角形的V4和V5的位置进行交换,我们就得到了逆时针环绕。下图是两个三角形,它们的缠绕方向相反。

3

在默认情况下,OpenGL认为具有逆时针方向环绕的多边形是正面的。这意味着图3的左侧是三角形的正面,而右侧是三角形的背面。

我们常常希望为一个多边形的正面和背面分别设置不同的物理特征。我们可以完全隐藏一个多边形的背面,或者给它设置-种不同的颜色和反射属性。纹理图像在背面三角形中也是相反的。在一个场景中,使所有的多边形保持环绕方向的一致,并使用正面多边 形来绘制所有实心物体的外表面是非常重要的。

如果想改变OpenGL的这个默认行为,可以调用下面这个函数。

/*
GL_CW:顺时针环绕的多边形将为正面
GL_CCW:逆时针环绕的多边形将为正面
*/
g1FrontFace(GL_CW) ;

油画法

对于这个问题,一个可能的解决办法是,对这些三角形进行排序,并且首先渲染那些较远的三角形,再在它们上方渲染那些较近的三角形。这种方式称为“油画法" ( painters algorithm )。

油画法

这种方法在计算机图形处理中是非常低效的,主要原因有两个。

  1. 必须对任何发生几何图形重叠地方每个像素进行两次写操作,而在存储其中进行写操作会使速度变慢。
  2. 对独立的三角形进行排序的开销会过高。

油画法弊端:如果三个三角形是叠加的情况,油画法将无法处理!

正面和背面剔除

在任何情况下,我们都应该只能看到正面,看不到背面,那为何还要浪费资源绘制背面呢?对三角形的区分正面和背面的原因之一就是为了剔除。背面剔除能够极大的提高性能,并修正图1出现的问题。这种方式是非常高效的,在渲染的图元装配阶段就整体抛弃了一些三角形,并没有执行任何不恰当的光栅化操作。

使用glEnableglDisable函数即可设置表面剔除功能。

glEnable(GL_CULL_FACE);    //开启
glDisable(GL_CULL_FACE) ;  //关闭

指明剔除的是正面还是背面调用函数glCullFace

/*
mode的可选值:
GL_FRONT
GL_BACK
GL_FRONT_AND_BACK
*/
void glCullFace(GLenum mode);

表面剔除后的效果

表面剔除后的效果

深度测试

仔细观察不难发现我们在进行表面剔除后仍然会有一些不现实的画面。

就算背面剔除能够消除位于对象背面的三角形,那么如果是重叠的独立对象又该怎么办呢?我们之前提到过油画法,这种方法是根据一种油画使用的技术而得名的。我们只要先简单地绘制背景,再在上面绘制较近的对象。这样做可能只要在画布上进行次数不多的绘制( 在手工绘制时更加有用),但对于图形硬件来说,这样做会导致在同一个片段区域重复进行绘制,而每一次绘制都会产生性能开销。如果开销过大则导致光栅化过程变慢,我们将这种方式称为“填充受限”。但是将油画法颠倒过来使用,实际上将会加速填充性能。首先绘制那些离观察者较近的对象,然后再绘制那些较远的对象。

深度测试将消除那些应该被已存在像素覆盖的像素,这将节省可观的存储器带宽。

深度测试是另外一种高效消除隐藏表面的技术。它的概念很简单:在绘制一 个像素时,将一个值(称为z值)分配给它,这个值表示它到观察者的距离。然后,当另外一个像素需要在屏幕上的同样位置进行绘制时,新像素的z值将与已经存储的像素的z值进行比较。如果新像素的z值比较大,那么它距离观察者就比较近,这样就在原来的像素上面,所以原来的像素就会被新的像素覆盖。如果新像素的z值更低,那么它 就必须位于原来像素的后面,不能遮住原来的像素。在内部,这个任务是通过深度缓冲区实现的,它存储了屏幕上每个像素的深度值。

我们在使用GLUT设置OpenGL窗口的时候,应该请求一个深度缓冲区并启用深度测试。

//申请一个颜色缓冲区和一个深度缓冲区。
glutInitDisplayMode(GLUT_DOUBLEI_GLUT_RGBA | GLUT_DEPTH);
//启用深度测试
glEnable(GL_DEPTH_TEST);

如果没有深度缓冲区,那么启用深度测试的命令将被忽略。

开启深度测试的效果