参考教程
【中文版】面剔除
【英文版】Face culling
学习记录
这一篇文章内容很少,其实我并不想将这一个放一篇文章的,毕竟我这并不是什么教程(Emm 也没人看)。这篇内容就是在渲染过程中进行的一部分优化。我们在渲染一个模型的时候,都是每个面都进行渲染,但是以我们的视角来看,有一部分面是根本不可见的。我们却要对它进行渲染,这样浪费了很多的时间。
尝试在脑子中想象一个3D立方体,数数你从任意方向最多能同时看到几个面。如果你的想象力不是过于丰富了,你应该能得出最大的面数是3。你可以从任意位置和任意方向看向这个立方体,但你永远不能看到3个以上的面。所以我们为什么要浪费时间绘制我们不能看见的那3个面呢?如果我们能够以某种方式丢弃这几个看不见的面,我们能省下超过50%的片段着色器执行数!
我说的是超过50%而不是50%,因为从特定角度来看的话只能看见2个甚至是1个面。在这种情况下,我们就能省下超过50%了。
这是一个很好的主意,但我们仍有一个问题需要解决:我们如何知道一个物体的某一个面不能从观察者视角看到呢?
如果我们想象任何一个闭合形状,它的每一个面都有两侧,每一侧要么面向用户,要么背对用户。如果我们能够只绘制面向观察者的面呢?
OpenGL 能够只渲染面向观察点的面而抛弃背向我们的那一边,面剔除默认是禁用的,要启用它使用 glEnable() 方法:
1 | glEnable(GL_CULL_FACE); |
当然,仅仅启动也是使用默认的面剔除类型,要自定义剔除面需要使用方法:
1 | glCullFace(GL_FRONT); |
glCullFace函数有三个可用的选项:
GL_BACK
:只剔除背向面。GL_FRONT
:只剔除正向面。GL_FRONT_AND_BACK
:剔除正向面和背向面。
glCullFace的初始值是GL_BACK。
OpenGL 能够剔除背面,但是需要我们告诉他哪边是正面那边是背面。这里有一个很聪明的技巧,分析顶点数据的环绕顺序(Winding Order)。
1 | float vertices[] = { |
当我们定义一组三角形顶点时,我们会以特定的环绕顺序来定义它们,可能是顺时针(Clockwise)的,也可能是逆时针(Counter-clockwise)的。每个三角形由3个顶点所组成,我们会从三角形中间来看,为这3个顶点设定一个环绕顺序。
可以看到,我们首先定义了顶点1,之后我们可以选择定义顶点2或者顶点3,这个选择将定义了这个三角形的环绕顺序。上边的代码展示了这点。
每组组成三角形图元的三个顶点就包含了一个环绕顺序。OpenGL在渲染图元的时候将使用这个信息来决定一个三角形是一个正向三角形还是背向三角形。默认情况下,逆时针顶点所定义的三角形将会被处理为正向三角形。
当你定义顶点顺序的时候,你应该想象对应的三角形是面向你的,所以你定义的三角形从正面看去应该是逆时针的。这样定义顶点很棒的一点是,实际的环绕顺序是在光栅化阶段进行的,也就是顶点着色器运行之后。这些顶点就是从观察者视角所见的了。
观察者所面向的所有三角形顶点就是我们所指定的正确环绕顺序了,而立方体另一面的三角形顶点则是以相反的环绕顺序所渲染的。这样的结果就是,我们所面向的三角形将会是正向三角形,而背面的三角形则是背向三角形。下面这张图显示了这个效果:
在顶点数据中,我们将两个三角形都以逆时针顺序定义(正面的三角形是1、2、3,背面的三角形也是1、2、3(如果我们从正面看这个三角形的话))。然而,如果从观察者当前视角使用1、2、3的顺序来绘制的话,从观察者的方向来看,背面的三角形将会是以顺时针顺序渲染的。虽然背面的三角形是以逆时针定义的,它现在是以顺时针顺序渲染的了。这正是我们想要剔除(Cull,丢弃)的不可见面了!
在顶点数据中,我们定义的是两个逆时针顺序的三角形。然而,从观察者的方面看,后面的三角形是顺时针的,如果我们仍以1、2、3的顺序以观察者当面的视野看的话。即使我们以逆时针顺序定义后面的三角形,它现在还是变为顺时针。它正是我们打算剔除(丢弃)的不可见的面!
也就是说,我们使用顶点的环绕顺序来判断面向,当我们定义逆时针顺序的三角形时候,以摄像机面向而言,我们所观察到的逆时针方向的三角形为正面,顺时针为反面,反面则被弃掉。
当然,OpenGL 仍然给我们提供了修改默认面向的方法:
1 | glFrontFace(GL_CCW); |
默认值是GL_CCW,它代表的是逆时针的环绕顺序,另一个选项是GL_CW,它(显然)代表的是顺时针顺序。
这样,我们可以设置面向的判断顺序(顺时针环绕顺序还是逆时针环绕顺序),设置抛弃的面向(正面还是反面)。启动面剔除后就可以将想剔除的面抛弃掉。