好消息和坏消息。好消息是,这个问题并不罕见,甚至不仅仅限于Unity。坏消息是,它之所以不罕见或不与Unity独有,是因为这是一个普遍存在的问题,没有完美的解决方案。但我们可能能够为您找到一个解决方法,所以让我们一起来解决这个问题。
在3D图形中存在一个根本性问题:你按什么顺序绘制物体?如果你在现实生活中画一幅普通的图片,显而易见的答案是你按照离观察者的距离远近来绘制物体。这个方法一段时间内效果还不错,但是对于那些不干净地“在前面”的物体怎么办呢?请考虑以下图片:
![A photograph of a basket containing fruit](https://istack.dev59.com/4jYR4.webp)
那个篮子里的水果是在碗前面还是后面?有点两者都不是,对吧?而且即使你能将物体分成前面和后面,如何处理相交的物体呢?这时就需要用到Z缓冲器:
![A simple 3D scene containing several meshes above a greyscale representation of the Z-Buffer of that scene](https://istack.dev59.com/bcU4D.webp)
Z-Buffer是一个简单的想法:在绘制对象的像素时,还要绘制这些像素的深度,也就是它们距离相机的远近。当你在场景中绘制一个新对象时,你会检查底层像素的深度,并将其与新像素的深度进行比较。如果新像素更接近,你就覆盖旧像素。如果旧像素更接近,你就不做任何处理。Z Buffer通常是一个单通道(即灰度)图像,从未直接显示。除了深度排序外,它还可以用于各种后期处理效果,如雾或环境光遮蔽。
现在,深度缓冲区的一个关键组成部分是它只能存储每个像素一个值。大多数情况下,这很好;毕竟,如果你只是想对一堆对象进行排序,你真正关心的只是最前面对象的深度。任何在最前面对象后面的东西都将被渲染为不可见,这就是你需要知道或关心的全部。
也就是说,除非你最前面的对象是透明的。
![Four images illustrating various intersecting transparent planes](https://istack.dev59.com/ImsJl.webp)
问题在于渲染器不知道如何处理绘制在透明对象后面的对象。为了避免这种情况,聪明的渲染器(包括Unity)会按照以下步骤进行:
1. 以任意顺序绘制所有不透明对象。
2. 按照与摄像机的距离对所有透明对象进行排序。
3. 从最远到最近绘制所有透明对象。
这样,遇到奇怪的深度排序问题的可能性就被最小化了。但在某些情况下,这仍然会出现问题。当您的对象使用透明材质时,实际上99%的对象是固体的,这一点是完全无关紧要的。就Unity而言,整个对象都是透明的,因此它根据其相对于场景中其他透明对象的深度进行绘制。如果您有大量透明对象,并且网格相交,那么您将立即遇到问题。
那么,如何解决这些问题呢?您有几个选择。
首先,也是最重要的一点,您可以将透明材质限制在明确透明的区域。我认为渲染顺序首先基于材质,因此具有多个不透明材质和单个透明材质的网格可能会正常工作,不过这只是我的猜测。
其次,如果您有其他选择,请使用它们。 "Cutout"模式之所以似乎是二进制掩码而不是真正的透明度,是因为它确实如此。由于它不是真正的透明,您不会遇到通常会遇到的深度排序问题。这是一种权衡。
第三,尽量避免使用透明材料的大型相交对象。大水体在这方面往往会引起问题。仔细考虑你所要处理的内容。
最后,如果你确实需要多个大型相交透明物体,请考虑将它们分成多个部分。
我知道这些答案都不是真正令人满意的,但关键是要认识到这不是一个 bug,而是媒介的限制。如果你非常想解决这个问题,可以尝试挖掘自定义渲染管线,但请记住,如果这样做,你将付出更多的性能代价。
祝好运。
Fade
。 - derHugo