对透明度混合技术,需要关闭深度写入,此时我们就需要小心处理透明物体的渲染顺序。那么,我们为什么要关闭深度写入呢?如果不关闭深度写入,一个半透明表面背后的表面本来是可以透过它被我们看到的,但由于深度测试时判断结果是该半透明表面距离摄像机更近,导致后面的表面将会被剔除,我们也就无法透过半透明表面看到后面的物体了。但是,我们由此就破坏了深度缓冲的工作机制,而这是一个非常糟糕的事情,尽管我们不得不这样做。关闭深度写入导致渲染顺序将变得非常重要。
我们来考虑不同的渲染顺序会有什么结果。
第一种情况,我们先渲染B,再渲染A,那么由于不透明物体开启了深度测试和深度写入,而此时深度缓冲中没有任何有效数据,因此B首先会写入颜色缓冲和深度缓冲。随后我们渲染A,透明物体仍然会进行深度测试,因此我们发现和B相比A距离摄像机更近,因此,我们会使用A的透明度来和颜色缓冲中的B的颜色进行混合,得到正确的半透明效果。
第二种情况,我们先渲染A,再渲染B。渲染A时,深度缓冲区中没有任何有效数据,因此A直接写入颜色缓冲,但由于半透明物体关闭了深度写入,因此A不会修改深度缓冲。等到渲染B时,B会进行深度测试,它发现,“咦,深度缓存中还没有人来过,那我就放心地写入颜色缓冲了!”,结果就是B会直接覆盖A的颜色。从视觉上来看,B就出现在了A的前面,而这是错误的。
从这个例子可以看出,当关闭了深度写入后,渲染顺序是多么重要。由此我们知道,我们应该在不透明物体渲染完之后再渲染半透明物体。那么,如果都是半透明物体,渲染顺序还重要吗?答案是肯定的。还是假设场景里有两个物体A和B,A在B前面。其中A和B都是半透明物体。
我们还是考虑不同的渲染顺序有什么不同结果。
第一种情况,我们先渲染B,再渲染A。那么B会正常写入颜色缓冲,然后A会和颜色缓冲中的B颜色进行混合,得到正确的半透明效果。
第二种情况,我们先渲染A,再渲染B。那么A会先写入颜色缓冲,随后B会和颜色缓冲中的A进行混合,这样混合结果会完全反过来,看起来就好像B在A的前面,得到的就是错误的半透明结构。
从这个例子可以看出,半透明物体之间也是要符合一定的渲染顺序的。
基于这两点,渲染引擎一般都会先对物体进行排序,再渲染。常用的方法是。
(1) 先渲染所有不透明物体,并开启它们的深度测试和深度写入。
(2) 把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。
那么,问题都解决了吗?不幸的是,仍然没有。在一些情况下,半透明物体还是会出现“穿帮镜头”。如果我们仔细想想的话,上面给出的第2步中渲染顺序仍然是含糊不清的——“按它们距离摄像机的远近进行排序”,那么它们距离摄像机的远近是如何决定的呢?读者可能会马上脱口而出,“就是距离摄像机的深度值嘛!”但是,深度缓冲中的值其实是像素级别的,即每个像素有一个深度值,但是现在我们对单个物体级别进行排序,这意味着排序结果是,要么物体A全部在B前面渲染,要么A全部在B后面渲染。但如果存在循环重叠的情况,那么使用这种方法就永远无法得到正确的结果。
由于多个物体互相重叠,我们不可能得到一个正确的排序顺序。这种时候,我们可以选择把物体拆分成两个部分,然后再进行正确的排序。但即便我们通过分割的方法解决了循环覆盖的问题,还是会有其他情况来“捣乱”。
这里的问题是:如何排序?我们知道,一个物体的网格结构往往占据了空间中的某一块区域,也就是说,这个网格上每一个点的深度值可能都是不一样的,我们选择哪个深度值来作为整个物体的深度值和其他物体进行排序呢?是网格中点吗?还是最远点?还是最近点?不幸的是,对于某些情况,选择哪个深度值都会得到错误的结果,我们的排序结果是A在B的前面,但实际上A有一部分被B遮挡了。这也意味着,一旦选定了一种判断方式后,在某些情况下半透明物体之间一定会出现错误的遮挡问题。这种问题的解决方法通常也是分割网格。
尽管结论是,总是会有一些情况打乱我们的阵脚,但由于上述方法足够有效并且容易实现,因此大多数游戏引擎都使用了这样的方法。为了减少错误排序的情况,我们可以尽可能让模型是凸面体,并且考虑将复杂的模型拆分成可以独立排序的多个子模型等。其实就算排序错误结果有时也不会非常糟糕,如果我们不想分割网格,可以试着让透明通道更加柔和,使穿插看起来并不是那么明显。我们也可以使用开启了深度写入的半透明效果来近似模拟物体的半透明。