背面剔除
介绍
它的基本思想是,大多数物体都会面向我们的封闭曲面,而背对着我们的面会被面向我们的面遮挡。顾名思义,我们不会渲染那些背对着我们的三角形。为了判断哪些面背对着我们,哪些面没有背对着我们,我们需要计算三角形的法向量,用于背面剔除的法向量与我们上次讨论的顶点法线信息不同。这些法线是由美术人员用来创建 3D 模型的导出程序生成的,它们试图以更高的分辨率封装曲面曲率信息。三角形表示,但它们试图伪造比三角形实际分辨率更高的信息。这些法线信息将用于光照计算,所以这不是我们这里要讨论的重点。在这种情况下,我们将计算每个三角形的法线,法线只是一个简单的向量,它垂直于三角形表面本身。我们可以使用叉积来计算它。在三角形上选择一个特定点,计算到另外两个点的向量,然后使用叉积来计算法线。
叉积可以按上图方式计算:我们将粗体 i、粗体 j 和粗体 k 视为构成三维坐标系的单位向量,因此 、 和 是这两个向量彼此正交,计算这两个向量的方法很简单,只需计算点的差值,然后计算叉积即可。
叉积不是一个运算,所以我不能简单地改变元素的顺序。如果你改变叉积中元素的顺序来得到新的向量,最终得到的向量仍然是原来的向量,只是方向相反。
假设你的艺术家创建的 3D 对象从软件中导出时,顶点的排列方式是左手约定。这意味着,如果你伸出左手,从 V1 向量开始弯曲手指,一直弯曲到 V2 向量,你可以想象你的指尖指向 V2 向量,那么你的拇指指向的就是这个叉积生成的向量的方向。同样地,如果你伸出左手,手指朝这个方向伸展,然后弯曲手指,指尖朝向 V2,你的拇指会指向相反的方向,这样我们就得到了一个朝这个方向的向量。如果这个向量是按照右手坐标系存储的,那么它们的方向就会相反。
对于 3D 模型,我们究竟使用的是左手坐标系还是右手坐标系,这又是一个约定俗成的问题。无论你使用什么引擎,它的坐标可能都使用不同的约定。所以,如果你加载 3D 模型,并且在游戏引擎中启用了背面渲染,而物体却消失了,这可能表明模型使用的额坐标系与渲染软件所期望的坐标系不同。此外,我们用于背面渲染的法线与 3D 软件 Maya、3d Max 或 Blender 等导出的法线并不相同。这些导出的法线试图考虑,三角形所镶嵌的底层表面的一些信息。创建近似值,这些用于光照,它们与每个顶点提供的法线不同,也与每个三角形的法线或计算背面的法线不同。
背面剔除方法 1
所以,一旦我们有了这些法线,我们该如何处理它们呢?如果我们思考一下,就会被发现有两种情况。如果我们考虑一下,从每个三角形发出的法线(就像我们刚才描述的那样),如果我们将其与从三角形中心指向摄像机的向量进行比较,我们可以查看这些向量之间的角度。稍后我们会看到,我们可以通过计算点积来计算这些角度。因此,如果从三角形中心指向摄像机的这些向量之间的角度小于 90 度,那么这个朝向摄像机的三角形就是我们要绘制的。但另一方面,如果这个角度大于 90,则表明三角形朝向另一个方向,我们不会绘制它。
那么,我们来判断这个角度是大于还是小于 90 度。三角函数中有一个很方便的公式:
需要注意,在计算这个点积时,叉积中向量 a 和 b 的长度并不重要,我们不需要对它们进行任何归一化。正如我们将在后续看到的,光照计算中使用的所有方向向量都需要归一化为单位长度,它们的长度都必须为 1。这里我们不需要对任何东西进行归一化,因为这里的模无关紧要。我们正在检查这个物体是否大于零且小于零,这严格来说是一个符号检查。所以我想提一点:你可能会想忽略向量的点积,一开始你可能会想,为什么不在完成视图变换后直接查看法向量的 Z 轴部分,这样就能看出它是背对着我们还是朝向着我们呢?如果所有物体都像这样紧密地位于区域内,这样做是合理的。但现在想象一下,你有一个位于最外侧的面,这里有一个向量,如果你只看 Z 轴,在视图空间中它看起来像是朝向着摄像机,但如果你看实际的点积,你会发现角度大于 90 度,实际上摄像机看不到这个面。
何时执行背面剔除
可以在不同的地方执行背面剔除,我们刚才描述的方式更自然地是从以下角度思考的:视图变换不能在投影变换之后进行,就像之前讨论的那样。这需要一些额外的工作,因为视图变换中的世界坐标系会保持距离和角度不变,但在投影变换之后,一旦进入裁剪空间,这些距离和角度就会发生扭曲。虽然仍然可以进行背面剔除,但这需要一些技巧。你可以想象在世界坐标系和视图变换之间进行背面剔除,但这并没有什么实际优势。为了节省计算量,世界坐标系和视图变换通常会预先相乘它们的矩阵。关键是要确保你在正确的坐标系中进行操作,这对于背面剔除和光照处理都至关重要。实际上,你甚至可以在对各种坐标进行世界变换之前就进行背面剔除。在这种情况下,你需要获取摄像机向量,也就是描述摄像机的参数,这些参数通常在世界空间中定义,通常由游戏逻辑、关卡编辑器或关卡设计师控制。你需要对这些参数进行逆世界变换,从而将这些摄像机重新放回物体中。这原本是你的美术师使用的空间,这听起来可能很奇怪,但可以提高效率,尤其是在以前 CPU 比 GPU 处理更多计算任务的时候。这种技术你可能在早期的游戏引擎中找到,或者在早期的图形学教科书中看到过。
如今,这种优化通常在更下游的流程中进行。你可以使用一些技巧,根据最终二维空间中各个三角形坐标的出现顺序来决定是否调用某个三角形。这是因为如今 GPU 的性能非常强大,通常情况下,最好将大量数据发送到显卡,让显卡在顶点层面进行计算,然后在最终的像素着色器阶段(即用光照计算填充三角形)之前,再决定是否调用某个三角形。
最后我想提的是,有些情况下你可能没有实体 3D 物体,而只是一些漂浮在空中的薄片,比如一块由各种三角形组成的布料。这个物体可能会以各种方式飘动,因此玩家有时可能会看到三角形的另一面。你可能会想,我们渲染这类物体时就关闭背面剔除,只在渲染实体物体时才启用它。但这种方法通常效果不佳,通常会导致后续的光照计算出错。除非你在着色器中进行一些自定义处理,否则你几乎总是应该直接复制所有三角形,然后让复制出的三角形的法线指向相反的方向。