第七章 透明效果(1)

@学习

1.透明效果

透明是游戏中常常要使用的一种效果,在实时渲染中要实现透明效果,一般会在渲染模型时控制它的透明通道(Alpha Channel)。当开启透明混合后,当一个物体被渲染到屏幕上时,每一个片元除了颜色值和深度值外,它还有另外一个属性——透明度。当透明度为1时,表示该像素是彻底不透明的,而当其为0时,则表示该像素彻底不会显示。
在Unity中,咱们一般使用两种方式来实现透明效果:第一种是使用透明度测试(Alpha Test),这种方法其实没法获得真正的半透明效果;另外一种是透明度混合(Alpha Blending)。
在以前的学习中,咱们历来没有强调过渲染顺序的问题。也就是说,当场景中包含不少模型时,咱们没有考虑是先渲染A,再渲染B,最后再渲染C,仍是按照其余的顺序来渲染。事实上,对于不透明(opaque)物体,不考虑它们的渲染顺序也能获得正确的排序效果,这是因为强大的深度缓冲(depth buffer,也被称为z-buffer)的存在。在实时渲染中,深度缓冲是用于解决可见性(visibility)问题的,它能够决定哪些物体哪些部分会被渲染在前面,而哪些部分又会被其它物体遮挡。它的基本思想是:根据深度缓冲中的值来判断该片元距离摄像机的距离,当渲染一个片元时,须要把它的深度值和已经存在于深度缓冲区中的值进行比较(若是开启了深度测试),若是它的值距离摄像机更远,那么说明这个片元不该该被渲染到屏幕上(有物体挡住了它);不然这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(若是开启了深度写入)。
使用深度缓冲,可使咱们没必要关心不透明物体的渲染顺序,例如A挡住了B,即使咱们先渲染A再渲染B也不用担忧B会遮盖掉A,由于在进行深度测试时会判断出B距离摄像机更远,也就不会写入到颜色缓冲中。但要实现透明效果,事情就不那么简单了,这是由于当使用透明度混合时,咱们关闭了深度写入(ZWrite)。
简单来讲透明度测试和透明度混合的基本原理以下:
(1)透明度测试:
它是一种“霸道极端”的机制,只要一个片元的透明度不知足条件(一般是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元不会再进行任何处理,也不会对颜色缓冲产生任何影响;不然就会按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。也就是说,透明度测试是不须要关闭深度写入的,它和其它不透明物体最大的不一样是它会根据透明度来舍弃一些片元。虽然简单,可是它产生的效果也很极端,要么彻底透明,即看不到,要么彻底不透明,就像不透明物体那样。
(2)透明度混合:
这种方法能够获得真正的半透明效果。它会使用当前片元的透明度做为混合因子,与已经存储在颜色缓冲中的颜色进行混合,获得新的颜色。可是透明度混合须要关闭深度写入,这使咱们要很是当心物体的渲染顺序。须要注意的是,透明度混合子关闭了深度写入,但没有关闭深度测试。这意味着当使用透明度深度混合渲染一个片元时,仍是会比较它的深度值与当前深度缓冲区中的深度值,若是它的深度值距离摄像机更远,那么就不会再进行混合操做。这一点决定了,当一个不透明物体出如今一个透明物体前面,而咱们先渲染了不透明物体,它仍然能够正常地遮住不透明物体。也便是说,对于透明混合度来讲,深度缓冲是只读的。测试

2. 为何渲染顺序很重要

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

2.UnityShader的渲染顺序

Unity为了解决渲染顺序的问题提供了渲染队列(render queue)这一解决方案。咱们可使用SubShader的Queue标签来决定咱们的模型将归于哪一个渲染队列。Unity在内部中使用一系列整数索引来表示每一个渲染队列,且索引号越小表示越早被渲染。在Unity5中,Unity提早定义了5个渲染队列(与Unity5以前的版本相比多了一个AlphaTest渲染队列),固然在每一个队列中间咱们可使用其余队列。下表给出了这5个提早定义的队列以及它们的描述。
code

所以,若是咱们想要经过透明度测试来实现透明效果,代码中应该包含相似下面的代码:blog

SubShader{
Tags{"Queue"="AlphaTest"}
Pass{
...
}
}

若是咱们想要经过透明度混合来实现透明效果,代码中应该包含相似下面的代码:排序

SubShader{
Tags{"Queue"="Transparent"}
Pass{
ZWrite Off
......
}
}

其中,ZWrite Off用于关闭深度写入,在这里咱们选择把它写在Pass中。咱们也能够把它写在SubShader中,这意味着该SubShader下的全部Pass都会关闭深度写入。索引

相关文章
相关标签/搜索