文/张大伟
根据我的观察,很多Unity 用户统并没有掌握该如何在一个单一场景中使用多个Unity的摄像机,他们对这个概念缺乏一些了解。 “如果我只是想从一个角度看这个场景,为什么我需要使用多个摄像机?”这个问题初听上去很有道理,当多个摄像机从同一个角度捕捉场景的时候会使得它更加混乱。那为什么还要在同一个场景中同一个方向上使用多个摄像机?原因是有些复杂,但是在同一个场景中同一个方向上使用多个摄像机这个事情确实值得我们学习。它会帮助你用一种很容易的方式来创造非常震撼的视觉效果,而这些效果如果只使用一个摄像机就很难完成。
什么是Unity中的摄像机?
在我们继续讨论这个话题之前, 你必须明白Unity的摄像机到底是什么。当Unity渲染场景的时候,它需要大量的重要信息被设置好,但是让我们简化下这些需要提前设置好的重要信息的列表使其更加容易理解。我们要考虑的重要信息有:
要渲染的物体的列表
摄像机的视角(以及摄像机的位置、旋转、缩放、视野、裁剪等等)
如果你已经对这个问题有了一些经验。你可能已经注意到我并不是在谈论矩阵。让我们现在先忽略数学相关的东西。
要渲染的物体的列表就是场景中的所有物体,是不是这样?错了!每个摄像机只会渲染那些对它来说是可见的物体(依据视野、视野锥体剔除等等)以及那些实际在指定摄像机可见的层上的物体。
剔除遮罩使用层有选择地渲染一组对象。有关使用层的信息可以在官方文档找到。通常比较好的做法是,是把用户界面放到不同的层,然后用一个独立相机单独渲染UI层。为了使UI显示在其他相机视角的顶部,你还需要设置清除标记为“只清除深度缓冲区“,并确保UI相机的深度比其他相机高。
剔除遮罩可以被设置成任何东西,或者你可以设置哪些层应该被看到。这个属性用来选择性的渲染部分场景。如果游戏物体的层遮罩与摄像机的剔除遮罩进行相交操作后结果为0,那么这个游戏物体对于这个摄像机来说是不可见的。
如果按照这个图设置的话,这个摄像机现在可以看到所有的物体。
这台摄像机只能看到默认层(地面那一层)和标记为红色的层(红球所在的层)里面的物体。
所得出的结论是不同的摄像机可以渲染不同的对象。而这是一条你现在还不知道该如何在实践中使用的重要信息。这也意味着,在场景中添加第二个摄像机不会导致重新绘制场景两次。只有对于第二个摄像机来说是可见的物体才会被渲染出来。有多个不同的摄像机分别渲染不同的层和用一个摄像机来渲染所有的层的效率是完全一样的,请认识到这一点。
那么,接下来让我们回答下主要的问题,也就是Unity的摄像机到底是什么。摄像机是一种设备,可以在给定的视角来渲染一组给定的物体。
摄像机到底在渲染什么?
等一下,我们不是刚回答了这个问题么?嗯......不完全是。场景中的物体分为可见和不可见的部分。你能看到的是最后作为结果输出的图像(让我们称它为颜色缓冲区)。当然还有一些东西是你看不到,这个看不到的东西被称为深度缓冲区(也被称为z buffer)。
深度缓冲区可以很容易的被描述为一个游戏屏幕大小的灰度图像,这个图像中的每个像素都代表着这个像素到摄像机到底有多远(实话实说这个解释不是100%正确的,但是我们认为现在不应该为了追求准确性而把这个概念解释的过于复杂)。深度缓冲区是由显卡的GPU来使用的,用来决定要被渲染的像素是否应该被处理还是应该从渲染中移除。结果就是,会被其他像素遮挡住的像素不会是可见的。
这个机制就跟现实世界中的机制是完全一样的,由于遮挡关系,我们看到不透明物体后面的内容,这个机制在计算机图形学中有个专门的名字叫画面算法。画家算法也叫作优先填充,它是三维计算机图形学中处理可见性问题的一种解决方法。当将三维场景投影到二维平面的时候,需要确定哪些多边形是可见的,哪些是不可见的。“画家算法”表示头脑简单的画家首先绘制距离较远的场景,然后用绘制距离较近的场景覆盖较远的部分。画家算法首先将场景中的多边形根据深度进行排序,然后按照顺序进行描绘。这种方法通常会将不可见的部分覆盖,这样就可以解决可见性问题。在有些场合下,画家算法可能无法解决可见性问题。在这个例子中,多边形 A、B、C 互相重叠,我们无法确定哪一个多边形在上面,哪一个在下面,我们也无法确定两个多边形什么时候在三维空间中交叉。在这种情况下必须用一些方法对这些多边形进行切分、排序。1972年提出的Newell算法就是切分类似多边形的一种方法,在计算几何领域人们已经提出了许许多多的解决方法。一些基本的画家算法实现方法也可能效率很低,因为这将使得系统将可见多边形集合中的每个点都进行渲染,而没有考虑这些多变性在最终场景中可能被其它部分遮挡。这也就是说,对于细致的场景来说,画家算法可能会过度地消耗计算机资源。人们有时候也使用逆向画家算法进行处理,这种算法首先绘制距离观察者较近的物体,已经进行绘制的部分不再进行其它的绘制过程。在计算机图形系统中,这种方法由于无需根据光照、纹理等参数计算被较近物体遮挡的远处物体的颜色,所以效率非常高。但是,这种方法也有许多与普通画家算法同样的问题。画家算法的这些缺陷导致了深度缓冲技术的发展,深度缓冲技术可以看作是画家算法的一个发展,它根据逐个像素的信息解决深度冲突的问题,并且抛弃了对于深度渲染顺序的依赖。即使在这样的系统中,有时也使用画家算法的变体。由于深度缓冲实现通常是基于硬件中的固定精度深度缓冲寄存器,因此舍入误差就会带来一些显示问题,即在多边形连接的地方会出现重叠或者间隙。为了避免这种问题,一些图形处理引擎使用了“过度渲染”的方法,即根据画家算法的顺序绘制两个多边形中受影响的边界。这也就是说有些像素如同在画家算法中那样实际上绘制了两次,但是由于图像中只有很少的一部分才做这样的处理,因此对于性能的影响很小。
摄像机的顺序和清理屏幕
在渲染场景的内容到颜色缓冲区和深度缓冲区之前,摄像机可以清除两个缓冲区的内容或者单独清理深度缓冲区的内容。你有没有注意到,默认情况下Unity5场景摄像机会将颜色缓冲区的内容清除到天空盒的缓冲区里里面去?
每个摄像机在渲染时会存储颜色和深度信息。屏幕的未绘制部分是空的,默认情况下会显示天空盒。当你使用多个摄像机的时候,每一个摄像机都将自己的颜色和深度信息存储在缓冲区中,还将积累大量的每个摄像机的渲染数据。当场景中的任何特定摄像机进行渲染时,你可以设定清除标记以清除缓冲区信息的不同集合。可以通过下面四个选项之一来完成:
关于清除标记的话,这里还有一些更多的选择:
“迁移到天空盒缓冲区并清除” 这是默认设置。在屏幕上空的部分将显示当前相机的天空盒。如果当前相机没有设置天空盒,它会默认使用渲染设置(在Edit->Render Settings里)中选择的天空盒。然后它将退回使用背景颜色。另外天空盒组件可以添加到相机上。如果你想创建一个新的天空盒,可以参考官方文档。
“固定的颜色”这个选项会把屏幕上的任何空的部分显示这个固定颜色,其实就是用这个固定的颜色先来设置颜色缓冲区,然后再进行渲染,这样没有被渲染的地方就是原来设置的固定的颜色。
“只清除深度缓冲区“这个选项将不会清理任何颜色缓冲区里面的内容,只会清理你的深度缓冲区的内容。如果你想绘制一个玩家的枪而不让它在环境内部得到裁剪,你要设置一个深度为0的相机来绘制环境,还要另一个深度为1的相机单独绘制武器。该武器相机的清除标记应设置为仅深度。这将保持环境的图形显示在屏幕上,但会丢弃所有关于每个对象在三维空间中的位置的信息。当枪被绘制出来,不透明的部分将完全覆盖任何已绘制的事物,而不管枪到墙之间如何接近。
“不清除“该模式不清除任何颜色或深度缓存。其结果是,每帧绘制在下一帧之上,造成涂片效果。这不是用于游戏的典型方式,最好是与自定义着色器一起使用。
如果我们尝试设置默认的摄像机的清除标记为“不清除“的话会发生什么?恩,效果可能会非常有趣(在进入游戏播放模式以后我稍微移动了下摄像机)。
它看起来就像我们的球体本身重复了这么多次,它变成某种有边有角的奇怪东西。除此之外。场景中仍然是有一个红色球的(注意,蓝色层对于摄像机来说仍然是不可见的),游戏场景的图像看起来仍然是有效的。并没有任何形式的图像瑕疵。然而,我们成功地只通过一个物体来创建了有很多重复物体的效果。
这种情况的出现是因为在帧与帧切换的过程中颜色缓冲区没有被清空(在之前帧所渲染的颜色缓冲区的内容没有清除,所以转移到下一帧中来了,而且因为一直没有清除,所以一直累加,就出现了上图中的那些奇怪的形状),深度缓冲区的内容同样也没有清除。深度缓冲区的用途是记住已经渲染的物体的深度,因为没有清除,当Unity试图绘制另外一帧的时候深度缓冲区还是保留着这些内容。当球体要在已经被渲染的球体后面进行渲染的时候,那些因为遮挡而不可见的像素会被丢弃掉。在场景有很多物体的情况下,一个物体接着一个物体进行渲染的时候也会发生同样的事情。
如果你还是不明白发生了什么,请现在停止阅读并尝试自己动手实现一遍!创建一个新的场景,添加一个对象,设置摄像机的清除标志为“不清除“并不时的移动这个物体或者你的摄像机。(自己动手尝试的核心在于一直移动,或者移动物体,或者移动摄像机,这样不同帧之间绘制出来的内容才会有重叠也有不同,因为没有清除之前帧的颜色缓冲区和深度缓冲区,这样出来的效果才是既有重叠又有遮挡的神奇效果)。
使用这些清除选项到底有什么好处?
我假设你不希望你的游戏中出现这种效果。所以做这些清除有什么好处呢?现在让我们通过一个有两个摄像机的小例子来说明这个问题。
· 蓝色摄像机
清除标记: ” 迁移到天空盒缓冲区并清除“
剔除遮罩: 默认层, 蓝色层
深度: 0
上面的截图是蓝色摄像机会看到的内容。
· 红色摄像机
清除标记: ”不清除“
剔除遮罩:红色层
深度: 1
上面的截图是红色摄像机会看到的内容。
这里面还有一个新的参数:深度。深度限定了摄像机渲染的顺序。深度数值较小的摄像机将会比深度数值较大的摄像机先进行渲染。
让我们来看看Unity是如何一步一步的渲染这个场景的(再次强调下,这个过程的解释不是100%准确的,只是为了让我们更好的理解这个过程):
(下面的内容都是发生在蓝色摄像机上下文的东西)
颜色缓冲区的内容被清理到天空盒里面并清空颜色缓冲区
深度缓冲区被清空
平面(属于默认层的物体)和蓝色的球体(属于蓝色层的物体)都将被渲染出来
(下面的内容都是发生在红色摄像头上下文的东西)
没有缓冲区被清空,也就是颜色缓冲区和深度缓冲区的内容都保持不变
红色的球体(属于红色层的物体)将被渲染出来
所得到的结果就是你会得到一个场景。就像是只用一台单独的摄像机进行渲染的一样:
所以为什么要费这么大劲做这么一个事情呢?让我们尝试一件事情来说明下。让我们把红色摄像机的清除标记从“不清除”切换成“只清除深度缓冲区”:
哇,你看到了吗?由于深度缓冲区已经被清除,红色的球不知道它在屏幕上渲染的像素会被其他像素遮挡,所以它就像没有被遮挡一样渲染出来了。这意味着清除深度缓冲区会将待渲染的物体渲染在前面。当你想渲染三维的用户交互元素的时候,这个功能可能是超级有用的。
在《上古卷轴5:天际》中你可以看到背包中的物品是以三维物体的形式表现出来的。即使背景的物体表现的离摄像机更近也没有影响这些物品的正确渲染。
另外一个有趣的选项是将摄像机的效果只对特定层表现出来。让我们尝试对蓝色摄像机使用模糊效果,就像下面的截图所示的这样:
现在让我们把红色摄像机的清除标记切换成“不清除”并对蓝色摄像机应用一个不同的效果:灰度化。
最后请记住一点,如果你想要移动某个摄像机的话,你可能需要一次移动所有的摄像机(这就是为什么将所有的摄像机作为一个游戏物体的子对象是非常普遍的一个做法,这样移动起来就很简单而不需要一个个移动,既可以避免出错又减少了很多繁琐的操作)。但是有些时候可能只移动一个摄像机是想要做的操作。。。