一、什么是面剔除?
1.面剔除
尝试在头脑中想象一下有一个3D立方体,你从任何一个方向去看它,最多可以同时看到多少个面。如果你的想象力不是过于丰富,你最终最多能数出来的面是3个。你可以从一个立方体的任意位置和方向上去看它,但是你永远不能看到多于3个面。所以我们为何还要去绘制那三个不会显示出来的3个面呢。如果我们可以以某种方式丢弃它们,我们会提高片段着色器超过50%的性能!
正背面剔除方案,是OpenGL中针对图形绘制的一种技巧,主要用于处理立体图形绘制时,只绘制观察者能看到的部分,看不到的部分就丢弃不绘制,这种做法可以将渲染性能提高50%左右。我们所说的是超过50%而不是50%,因为从一个角度只有2个或1个面能够被看到。这种情况下我们就能够提高50%以上性能了。
这的确是个好主意,但是有个问题需要解决:我们如何知道某个面在观察者的视野中不会出现呢?如果我们去想象任何封闭的几何平面,它们都有两面,一面面向用户,另一面背对用户。假如我们只渲染面向观察者的面会怎样?
这正是面剔除(Face culling)所要做的。OpenGL允许检查所有正面朝向(Front facing)观察者的面,并渲染它们,而丢弃所有背面朝向(Back facing)的面,这样就节约了我们很多片段着色器的命令(它们很昂贵!)。我们必须告诉OpenGL我们使用的哪个面是正面,哪个面是反面。OpenGL使用一种聪明的手段解决这个问题——分析顶点数据的连接顺序(Winding order)。
2.隐藏面消除
任何一个3D的物体,比如立方体,球,多边形多面立方体,等等,就像地球有昼夜一样,太阳光永远只能照射在地球的一个面上,在任何情况下我们都只能看到其中的一个面,那么对于看不到的背面在计算机里图形学里面如果不被渲染,那么性能会提高50%,即使渲染了也不看不到,而且性能下降,所以,完全没有必要。在绘制3D场景的时候,我们需要决定哪些部分是对观察者 可见的,或者哪些部分是对观察者不可⻅的.对于不可见的部分,应该及早丢弃.例如在⼀个不透明的墙壁后,就不应该渲染.这种对于看不到的面不去渲染的情况叫做“隐藏面消除”
二、立方体中的正背面
任何物体都有两面性,正面和背面,而观察者只能看到一个面.OpenGL可以通过分析顶点数据的顺序检测到面向观察者的面从而渲染他们,丢弃背面的渲染.这样可与节约片元着色器的性能.
1.正背面的定义
- 正面:按照逆时针顶点连接顺序的三角形面
-
反面:按照顺时针顶点连接顺序的三角形面
2.顶点连接顺序分析
- 左侧三角形顶点顺序为: 1—> 2—> 3 ; 右侧三角形的顶点顺序为: 1—> 2—> 3 - 当观察者在右侧时,则右侧的三角形方向为逆时针方向则为正面,左侧的三角形为顺时针为背面
- 当观察者在左侧时,则左侧的三角形方向为逆时针方向为正面,右侧的三角形为顺时针为背面
GLfloat vertices[] = {
//顺时针
vertices[0], // vertex 1
vertices[1], // vertex 2
vertices[2], // vertex 3
// 逆时针
vertices[0], // vertex 1
vertices[2], // vertex 3
vertices[1] // vertex 2
};
正面和背面是由三角形的顶点定义顺序和观察者的方向共同决定的,随着观察者的角度方向改变,正面背面也会跟着改变。观察者的角度很好理解,站在不同的角度方位看一个物体,能看到的一面是正面,看不到的是背面。实际的顶点连接顺序是在光栅化阶段(Rasterization stage)计算的,所以当顶点着色器已经运行后。顶点就能够在观察者的观察点被看到。把所有三角的顶点都定义为逆时针是一个很好的习惯。
三、面剔除常用API
- 开启正背面剔除(默认是背面剔除)
void glEnable(GL_CULL_FACE)
从这儿以后,所有的不是正面朝向的面都会被丢弃(尝试飞入立方体看看,里面什么面都看不见了)。目前,在渲染片段上我们节约了超过50%的性能,但记住这只对像立方体这样的封闭形状有效。当我们绘制上个教程中那个草的时候,我们必须关闭面剔除,这是因为它的前、后面都必须是可见的。
- 关闭正背面剔除(默认是背面剔除)
void glDisable(GL_CULL_FACE)
- 用户选择剔除哪个面(正面/背面)
OpenGL允许我们改变剔除面的类型。要是我们剔除正面而不是背面会怎样?我们可以调用glCullFace
来做这件事:
void glCullFace(<#GLenum mode#>)
glCullFace函数有三个可用的选项:
GL_BACK:只剔除背面(默认值)。
GL_FRONT:只剔除正面。
GL_FRONT_AND_BACK:剔除背面和正面。
- 用户指定顺时针或者逆时针为正面
另外,我们还可以告诉OpenGL使用顺时针而不是逆时针来表示正面,这通过glFrontFace来设置:
glFrontFace(GLenum mode)
glFrontFace(GL_CCW); // 逆时针为正面(默认值)
glFrontFace(GL_CW); // 顺时针为正面
- 我们可以做个小实验,告诉OpenGL现在顺时针代表正面:
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CW);
或者
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
上述两段代码功能相同
具体实现代码如下
//例如,剔除背⾯实现(1)
glCullFace(GL_BACK);
glFrontFace(GL_CW);
//例如,剔除正⾯实现(2)
glCullFace(GL_FRONT);
//以下两行是默认的,可以不写
glFrontFace(GL_CCW);
glCullFace(GL_BACK);
剔除正、背面举例如下:
// 剔除背面方式1 - 正常情况下使用默认的情况即可
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
//剔除背面方式2
glEnable(GL_CULL_FACE);
glFrontFace(GL_CW);
glCullFace(GL_FRONT);
//剔除正面方式1
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
//剔除正面方式2
glEnable(GL_CULL_FACE);
glFrontFace(GL_CW);
glCullFace(GL_BACK);
四、正背面剔除功能举例
- 未开启背面剔除功能
绘制甜甜圈模型,旋转甜甜圈,会出现如下情况:
- 开启正背面剔除功能
旋转甜甜圈又出现如下问题:
-
上面这种情况出现的原因是什么呢?
在甜甜圈旋转的过程中,当前后两部分重叠时,对于我们而言,需要展示的是前面的部分,后面部分是隐藏面,但是OpenGL并不能清楚的区分哪个面在前哪个面在后,因此出现了缺口。
- 那么如何解决这种问题呢?
利用深度测试解决。深度测试参考文章:android openGL ES详解——深度缓冲区_opengl 深度信息-CSDN博客
五、总结
隐藏面消除总结:
- 正背面消除:需要根据顶点数据顺序判断用户可见部分与隐藏面,隐藏面直接丢弃,不绘制,只绘制可见部分
- 深度测试:可以一次性解决隐藏面消除问题,原理是不管有多少图层,只显示可见图层,剩余不可见的都丢弃
正如你所看到的那样,面剔除是OpenGL提高效率的一个强大工具,它使应用节省运算。你必须跟踪下来哪个物体可以使用面剔除,哪些不能。
参考文章
OpenGL_正面剔除,深度测试和Z冲突 - 简书
OpenGL--- (四)OpenGL渲染技巧:正背面剔除 - 简书
Open GL 渲染技巧-正背面剔除、深度测试、颜色混合 - 简书
OpenGL--- (四)OpenGL渲染技巧:正背面剔除 - 简书
OpenGL---(五)OpenGL渲染技巧:深度测试、多边形偏移、混合 - 简书
面剔除 - LearnOpenGL-CN