OpenGL入门第5课--正背面剔除

1,297 阅读6分钟

      在上一节说深度测试的时候,举了个例子:物体A被物体B遮档了一部分,在不开启深度测试的情况下如果我们先绘制一个距离⽐较近的物体(B),再绘制距离较远的物体(A),则距离远的位图因为后绘制,会把距离近的物体覆盖掉。有人说那么可以先绘制较远的物体A,然后再绘制比较近的物体B,确实这样绘制是没有问题的,这也就是所谓的油画算法。但是,想象一下,如果采用油画算法被遮挡的那一部分像素是不是绘制了多次,这是不是就多了很多开销,浪费了不少GPU性能。所以必要的时候开启深度测试是一个很不错的选择。不过呢今天我要说的是另外一个提升性能的技巧——正背面剔除

为什么要使用正背面剔除

        假设有个正方体,想想从一个方向你最多能看到几个面。只要你不是透视眼不管你从哪个方向最多都不会超过3个面。那么对于那些看不到的面我们为什么要去绘制呢?

      我们可以假定把任何物体都只分为两个面,正面和背面。当前角度能够看到的叫做正面;看不到的叫做背面。而OpenGL正好可以做到检查所有正⾯朝向观察者的面,并渲染它们;丢弃背面朝向观察者的面. 这样可以节约片元着⾊器的性能.

环绕顺序

        那么如何告诉OpenGL你绘制的图形哪个是正面,哪个是背面呢?OpenGL使用了一个很聪明的技巧,分析顶点数据的环绕顺序。

      在定义一组三角形顶点时,会以特定的环绕顺序来定义它们,可能是顺时针(Clockwise)的,也可能是逆时针(Counter-clockwise)的。每个三角形由3个顶点所组成,我们会从三角形中间来看,为这3个顶点设定一个环绕顺序.如下图:

                             

       首先定义了顶点1,之后可以选择定义顶点2或者顶点3,这个选择将定义了这个三角形的环绕顺序。下面的代码展示了这点:

float vertices[] = {
    // 顺时针
    vertices[0], // 顶点1
    vertices[1], // 顶点2
    vertices[2], // 顶点3
    // 逆时针
    vertices[0], // 顶点1
    vertices[2], // 顶点3
    vertices[1]  // 顶点2  
};

        每组组成三角形图元的三个顶点就包含了一个环绕顺序。OpenGL在渲染图元的时候将使用这个信息来决定一个三角形是一个正向三角形还是背向三角形。默认情况下,逆时针顶点环绕所定义的三角形将会被处理为正向三角形。

         在定义顶点顺序的时候,你应该想象对应的三角形是面向你的,所以你定义的三角形从正面看去应该是逆时针的。这样定义顶点很棒的一点是,实际的环绕顺序是在光栅化阶段进行的,也就是顶点着色器运行之后。这些顶点就是从观察者视角所见的了。

         观察者所面向的所有三角形顶点就是我们所指定的正确环绕顺序,而另一面的三角形顶点则是以相反的环绕顺序所渲染的。这样的结果就是,我们所面向的三角形将会是正向三角形,而背面的三角形则是背向三角形。下面这张图显示了这个效果:

                         

      上图中对于左右两个三角形,我们在定义其顶点数据时,都应该以正向面对对应三角形的角度来定义,并且是逆时针定义。这样正面的三角形是1、2、3,背面的三角形也是1、2、3(如果我们从其对应正面看这个三角形的话))。然而,如果从观察者当前视角使用1、2、3的顺序来绘制的话,从观察者的方向来看,背面的三角形将会是以顺时针顺序渲染的。虽然背面的三角形是以逆时针定义的,它现在是以顺时针顺序渲染的了。这正是我们想要剔除(Cull,丢弃)的不可见面了!

相关API

     OpenGL为我们提供了便捷的剔除背面的API,但是默认情况下是禁用的,所以如果想要利用OpenGL的正背面剔除功能就必须提前开启它。

glEnable(GL_CULL_FACE);

        这一句代码之后,所有背向面都将被丢弃不再渲染。这样在渲染片段的时候能够节省50%以上的性能,但注意这只对像立方体这样的封闭形状有效。当在特定场景下需要某些2个面都可见的图形的时候,我们必须要再次禁用它,因为它们的正向面和背向面都应该是可见的。

glDisable(GL_CULL_FACE); //禁用正背面剔除

         OpenGL还允许我们改变需要剔除的面的类型。如果我们只想剔除正向面而不是背向面会怎么样?我们可以调用glCullFace来定义这一行为

glCullFace(GL_FRONT);//GL_FRONT 剔除正面   GL_BACK ,默认值,表示剔除背面  GL_FRONT_AND_BACK 2个面都剔除

       也可以通过调用glFrontFace,告诉OpenGL我们希望将顺时针的面(而不是逆时针的面)定义为正向面:

glFrontFace(GL_CCW);//默认值是GL_CCW,它代表的是逆时针的环绕顺序,另一个选项是GL_CW,它(显然)代表的是顺时针顺序

        正背面剔除对于不透明的凸面体是完美的, 但是对于透明物体,或者是凹面体则不适用。