背面剔除
背面剔除是一种常见的优化手段。对于特定的视角,如果查看的是物体的背面,就不进行后续渲染。在 Maya 里有类似的概念:单面显示和多面显示。比如在单面显示的情况下,模型内部的面就不会显示。
1. OpenGL 接口
在实现背面剔除之前,我们先了解一下 OpenGL 中与之相关的接口。
1. 我们使用 glEnable 接口,传递 GL_CULL_FACE 参数,启用剔除。
- void glEnable(GLenum cap);
2. glCullFace 接口指定剔除哪些面。虽然平时都是剔除背面,但是也可以指定剔除正面。GL_FRONT 剔除正面;GL_BACK 剔除背面。
- void glCullFace(GLenum mode);
3. glFrontFace 接口用于定义什么才是正面。GL_CW 表示,顶点按顺时针绘制时,为正面;GL_CCW 表示,顶点按逆时针绘制时,为正面。
- void glFrontFace(GLenum mode);
2. 判断顺时针与逆时针
现在最大的问题就是,如何判断指定的顶点顺序,是顺时针还是逆时针?现在假定我们的顶点顺序是“A-B-C”,那么如图 1 所示,左边是逆时针的情况,右边是顺时针的情况。
我们可以通过计算向量 AB 和 AC 的叉乘,判断是顺时针还是逆时针。二维平面上,叉乘结果为正,为逆时针;叉乘结果为负,为顺时针。同理,三维平面上,左手坐标系下,叉乘向量朝里,为逆时针;叉乘向量朝外,为顺时针。
三维叉乘的 z 分量,就是二维叉乘的结果。
Maya 里单面显示错误,会做反转法线的操作。
三维叉乘就是平面的法向量。可以看到逻辑有相通之处。
3. 代码实现
如代码清单 1 所示,我们首先实现 OpenGL 这套剔除的接口。内部就是一些状态的设置和记录,这边不赘述。
接着我们看如何判断是否剔除。因为现在可以指定剔除背面还是正面,正面的定义又可以是顺时针或逆时针,所以总共有四种情况。
我们用以下真值表来进行分析,表中是剔除背面的情况。我们以第一项进行说明:我们定义顺时针为正面。如果叉乘结果大于 0,代表顶点是逆时针,即为背面。我们现在要剔除背面,所以结果就是需要剔除。
从真值表中可以发现,这是异或关系。
z > 0 | TFrontFace::Clockwise | 结果 | 解释 |
---|---|---|---|
True | True | 剔除 | 法向指前,顶点逆时针;顺时针为正面,所以剔除(背面)。 |
True | False | 不剔除 | 法向指前,顶点逆时针;逆时针为正面,所以不剔除(正面)。 |
False | True | 不剔除 | 法向指后,顶点顺时针;顺时针为正面,所以不剔除(正面)。 |
False | False | 剔除 | 法向指后,顶点顺时针;逆时针为正面,所以剔除(背面)。 |
现在逻辑都理顺了,我们实现最终的剔除判断。如代码清单 2 所示,我们对向量 AB 和 AC 做叉乘。然后按照上述推导的结论做异或。最后按照指定的剔除面,返回是否剔除。
我们在三角形做光栅化之前,调用 ShouldCullTriangle 判断是否要剔除即可。如果需要剔除,就可以不继续后续的光栅化处理。
我们在之前旋转三角形的基础上,设置了背面剔除。如视频 1 所示,当三角形旋转到背面,就没有内容显示了。
完整的实验代码见 tag/culling。
我还有一个在意的点是,裁剪过后的点是否能“满足”剔除判断。情况如视频 2 所示,看着是没什么问题。
Sutherland-Hodgman 裁剪算法是按照原始输入顺序的。最终的输出,即使是添加了点,也能满足原始输入顺序。
后续的多边形拆分成多个三角形。是固定第一个点,以“扇形”的方式进行拆分。也能满足顶点的相对顺序。
顺序能满足,应该是没什么问题。同时实验结果也没问题。