赞
踩
透明是游戏中经常要使用的一种效果。在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道(Alpha Channel)。当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值之外,它还有另一个属性一一透明度。
当透明度为 1时,表示该像素是完全不透明的,而当其为0时,则表示该像素完全不会显示。在Unity 中我们通常使用两种方法来实现透明效果第一种是使用透明度测试(Alpha Test)这种方法其实无法得到真正的半透明效果;另一种是透明度混合(AIpha Blending)。
深度缓冲:
深度缓冲(depth buffer,也被称为z-buffer)的存在在实时染中深度缓冲是用于解决可见性(visibility)问题的,它可以决定哪个物体的哪些部分会被渲染在前面,而哪些部分会被其他物体遮挡。它的基本思想是:根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在于深度缓冲中的值进行比较(如果开启了深度测试),如果它的值距离摄像机更远,那么说明这个片元不应该被染到屏幕上(有物体挡住了它);否则,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(如果开启了深度写入)
透明度测试:它采用一种“霸道极端”的机制,只要一个片元的透明度不满足条件(通是小于某个阅值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它即进行深度测试、深度写入等。也就是说,透明度测试是不需要关闭深度写入的,它和他不透明物体最大的不同就是它会根据透明度来舍弃一些片元。虽然简单,但是它产生的效果也很极端,要么完全透明,即看不到,要么完全不透明,就像不透明物体那样。
透明度混合:这种方法可以得到真正的半透明效果。它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明度混合需要关闭深度写入(我们下面会讲为什么需要关闭),这使得我们要非常小心物体的渲染顺序。需要注意的是,透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄像机更远,那么就不会再进行混合操作。这一点决定了,当一个不透明物体出现在一个透明物体的前面,而我们先渲染了不透明物体,它仍然可以正常地遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的。
前面说到,对于透明度混合技术,需要关闭深度写入,此时我们就需要小心处理透明物体的渲染顺序。那么,我们为什么要关闭深度写入呢?如果不关闭深度写入,一个半透明表面背后的表面本来是可以透过它被我们看到的,但由于深度测试时判断结果是该半透明表面距离摄像机更近,导致后面的表面将会被剔除,我们也就无法透过半透明表面看到后面的物体了。但是,我们由此就破坏了深度缓冲的工作机制,而这是一个非常非常非常(重要的事情要讲3遍)糟糕的事情,尽管我们不得不这样做。关闭深度写入导致渲染顺序将变得非常重要。
第一种情况,我们先渲染 B,再渲染 A。那么由于不透明物体开启了深度测试和深度检验而此时深度缓冲中没有任何有效数据,因此 B 首先会写入颜色缓冲和深度缓冲。随后我们渲染A,透明物体仍然会进行深度测试,因此我们发现和 B 相比A 距离摄像机更近因此,我们会使用A的透明度来和颜色缓冲中的B的颜色进行混合,得到正确的半透明效果。
第二种情况,我们先渲染A,再染 B。渲染A时,深度缓冲区中没有任何有效数据因此A直接写入颜色缓冲,但由于对半透明物体关闭了深度写入,因此A 不会修改深度缓冲。等到渲染B 时,B 会进行深度测试,它发现,“咦,深度缓存中还没有人来过,那我就放心地写入颜色缓冲了!”,结果就是 B 会直接覆盖A的颜色。从视觉上来看,B就出现在了A的前面,而这是错误的。
我们还是考虑不同的渲染顺序有什么不同结果。。 第一种情况,我们先渲染 B,再染A。那么B 会正常写入颜色缓冲,然后A 会和颜色缓冲中的B颜色进行混合,得到正确的半透明效果。第二种情况,我们先渲染A,再染 B。那么A 会先写入颜色缓冲,随后B 会和颜色缓冲中的A进行混合,这样混合结果会完全反过来,看起来就好像B在A的前面,得到的就是错误的半透明结构。
考虑物体部分重叠的情况
(1)先渲染所有不透明物体,并开启它们的深度测试和深度写入。
(2)把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。
那么,问题都解决了吗?不幸的是,仍然没有。在一些情况下,半透明物体还是会出现“穿帮镜头”。如果我们仔细想想的话,上面给出的第2步中染顺序仍然是含糊不清的一–“按它们距离摄像机的远近进行排序”,那么它们距离摄像机的远近是如何决定的呢?读者可能会马上脱口而出,“就是距离摄像的深度值嘛!”但是,深度缓冲中的值其实是像素级别的,即每个像素有个深度值,但是现在我们对单个物体级别进行排序,这意味着排序结果是,要么物体A 全部在 B前面渲染,要么A全部在B 后面染。但如果存在循环重叠的情况,那么使用这种方法就永远无法得到正确的结果。
这个网格上每一个点的深度值可能都是不一样的,我们选择哪个深度值来作为整个物体的深度值和其他物体进行排序呢?是网格中点吗?还是最远的点?还是最近的点?不幸的是对于图8.4中的情况,选择哪个深度值都会得到错误的结果,我们的排序结果总是A在B的前面但实际上A有一部分被B遮挡了。
这也意味着,一旦选定了一种判断方式后,在某些情况下半透明物体之间一定会出现错误的遮挡问题。这种问题的解决方法通常也是分割网格。
尽管结论是,总是会有一些情况打乱我们的阵脚,但由于上述方法足够有效并且容易实现,因此大多数游戏引擎都使用了这样的方法。为了减少错误排序的情况,我们可以尽可能让模型是凸面体,并且考虑将复杂的模型拆分成可以独立排序的多个子模型等。其实就算排序错误结果有时也不会非常糟糕,如果我们不想分割网格,可以试着让透明通道更加柔和,使穿插看起来并不是那么明显。我们也可以使用开启了深度写入的半透明效果来近似模拟物体的半透明。
Unity为了解决渲染顺序的问题提供了染队列(render queue)这一解决方案。我们可以用SubShader的Queue标签来决定我们的模型将归于哪个染队列。Unity在内部使用一系列整数索引来表示每个渲染队列,且索引号越小表示越早被染。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。