目 录
8.1 光线的物理特性与人的感觉
8.2 颜色模拟
8.2.1 非彩色光
8.2.2 颜色模型的三个成分
8.3 照明模拟
8.3.1 环境照明
8.3.2 漫反射
8.3.3 镜面反射
8.4 在屏幕到世界中观察照明
8.5 辐射度
8.6 在世界到屏幕中观察照明
引言
我们很难对人类对光线的感知能力做出过高的评价。但在我们所拥有的所有的感觉器官中,眼睛无疑是最重要的一个。这或许是因为由眼睛获得的信息与人体其他器官所获得的信息相比有更大的带宽所造成的吧。正是由于这个原因,整个计算机图形学领域的内容都建立在了开发人类的视觉感知能力上。
在前面的介绍中,我们已经讨论了如何来描绘一个真实场景的大致轮廓。但是,如果我们对同一个场景使用不同类型的灯光的话,那么这个场景最后表现出来的效果将会大大不同。并且这种不同不仅仅表现在物体的表面有不同的颜色,也将影响到场景的反射和阴影模式(shadow pattern),它们的效果也是很明显的。
同时,颜色和光线在计算机图形学中也是两个很重要的主题。它们都横跨了多个领域,这些领域包括:几何学,光学,物理学等。我们将会讨论在计算机图形学程序中表现颜色的一些基本原理,以及关于光源与被照亮物体之间相互作用的一些话题。这些讨论都是基于自然界中实际处理这些情况的过程的基本原理。但是其中大多数都是最基本的,这一点也是可以理解的,主要是因为采用高级的光学和波动理论将会消耗很大的资源。这一点也有一定的缺陷,例如我们采用的反射模型,使用它将无法产生我们在真实世界中所看到的某些效果。但是在某种程度上,我们可以采用合成方法,它与真实情况相比较也是可以接受的。
在下面的讨论中,我们也将讨论一些基本的模拟颜色和光线的途径,特别是如何将世界中的光线处理到屏幕上。
8.1、光线的物理特性与人的感觉
我们使用计算机图形学方法进行渲染的最终目标就是要能够通过眼睛的处理。因此,我们就必须要充分考虑人眼是如何观察物体的。
是什么能使我们的眼睛区别红色与蓝色呢?在物理上,红色与蓝色又有什么区别呢?光在本质上一种电磁场,它在电磁特性上的一些参数决定了它的特性。这些电磁波根据频率或者波长的不同而表现出不同的特性。我们的眼睛只能看到波长为400到700纳米之间范围内的光线。
我们对两个眼睛接收的光线进行聚焦,每一个眼睛接收的图象是有所不同的,并且可以大致估计出物体到我们的距离。如果同一处物体的图像的投影完全一致的话,那么这个物体就在离我们无限远的地方。如果图象有一定的位移,那么物体的距离就会离我们近一些。我们交替的闭上眼睛,注视不同距离的物体就会明显的感觉到这一点。我们可以充分利用这一特点来产生沉浸式(immersive)的具有真实感的三维视觉图形,具体来说就是要保证我们的两个眼睛能够看到同一个场景的不同角度的图象。使用这种方法,我们就会感受到场景的深度信息。当然,我们的程序还必须依赖一些特殊的硬件才能达到上面的效果。这些硬件种类很多,从价格昂贵的头盔式显示器(head-mounted displays)(两个眼睛有各自不同的显示屏幕)到相对便宜的光闸眼镜(shutter glasses)(交替关闭透镜,从而使每一只眼睛能够同步的接收到计算机屏幕产生的不同的图象)。
我们所看到的图象在眼睛里被投影到视网膜上。视网膜上的视觉神经能够感受颜色。我们眼睛主要有两种感受光线的细胞:锥状细胞和杆状细胞。杆状细胞对光线有很强的感应能力而对颜色则没有直接的感觉。锥状细胞则对颜色存在感应能力。有三种不同的锥状细胞,每一种都只对某一特定的光谱范围有最强的感知能力。第一种对光谱中的蓝光敏感,第二种对绿光敏感,第三种则对红光敏感。对红光区域最敏感的细胞对相邻的光谱区域也有一定的感应能力,只是随着距离的增大而逐渐减小。人眼感应蓝光的细胞相对来说分布密度较小,因而感应能力也就比红光和绿光要小一些(见图8.1) 。这对于我们建立颜色调色板有一定的提示作用。通常我们都只分配较少的颜色位数来表示蓝色。
图8.1 不同颜色受体的敏感性范围 |
通常,上述三种分布综合起来决定了我们对不同波长光线的感应能力。对于人眼来说,最敏感的颜色位于光谱范围的中心附近。
单色光可以通过波长和强度来唯一的进行表示。但是真实的可见光都不是单色光 — 它们都是由不同波长和强度的光线组合而成的。实际光线可以用光线在可见光范围内的能量分布来表示。略微偏红的颜色的分布图会偏向右侧一些,而偏蓝的颜色会偏向左侧一些。
图8.2 某种颜色的能量分布 |
从物理学上来讲我们用能量分布来表示颜色,但是在实际的计算机图形学中我们没有必要这样做。考虑到人眼的一些特性,我们可以使用能量分布最大值处的波长 — 主波长(dominant wave length) — 来表示颜色。实际上,我们认为具有不同能量分布而又具有相同主波长的颜色具有相同的色度。而整个分布范围内能量的总量则涉及到了颜色的饱和度问题,例如,理想白色光的能量分布在整个光谱范围内是一条直线,它没有主波长,因此饱和度就是0%。一个具有100%饱和度的颜色只在某一位置上有一个能量冲击, 而在其他地方的能量均为0。光线的亮度则涉及到了能量的大小问题,特别是能量分布的优势部分的能量大小。
上面通过能量分布来描述颜色的假设对我们来说还是有些太复杂,并且也没有考虑到我们人眼视觉系统的一些特性,接下来,我们就要检验一下在计算机图形学程序中模拟颜色的实际的一些方法。
8.2、颜色模拟
人类为不同的物体再生出颜色的尝试可以一直追溯到几个世纪以前。对绘画颜色的混合以及彩色电视机的出现可能算作使这种尝试的成功典范了。在所有的尝试中,我们所使用的方案都要比能量分布来的简单,并且也都获得了可以接收的结果。下面,我们就来讨论一下计算机图形学程序中所使用的几种方案。
8.2.1 非彩色光(白与黑)
光线的总能量对我们的感觉来说是很重要的。我们在观察黑白图象以及将它们联系到现实生活中时,都间接的证明了这一点。较早的电视机与计算机显示器都是黑白的。黑白颜色如此受到欢迎主要是因为使用它可以简单的对任何颜色进行描述和重建。我们需要得到的仅仅是一个与光线能量大小相关的数字罢了。较高的能量转化为较亮的颜色,较低的能量则转化为较暗的颜色。
在我们表现光照效果时(例如反射效果,我们很快就会讨论到它),我们需要一种颜色的一整套不同的亮度等级。要得到这些亮度等级,我们就要给不同的亮度等级分配一些索引,这样就可以随着索引的增长而得到逐渐变亮的亮度等级。
通常,这些索引并不是简单地与光线的亮度成比例。它们实际上是成对数关系的。当能量较小时,我们需要索引的增量小一些,而当能量较大时,则需要增长得大一些。这主要是因为我们的视觉系统在能量等级较高时不容易区分亮度之间的差别。大多数情况下,计算机显示设备都会考虑到人眼的这一特性。
为非彩色光的亮度等级设置了索引之后,我们就可以执行某些光线操作了。例如,由于两个灯光合并起来的效果跟它们能量的和有关,所以我们就可以用它们索引的和来模拟它们合并后的效果。简单来说,我们可以在索引间进行内插,找到一个适合于已知亮度的索引值。
但是,我们将两个索引加起来之后得到的值很有可能会超过可用亮度等级的范围。当可用亮度等级的数量存在上限时,我们就要设计一些方法来保证任何颜色操作的结果都能够有一个正确的值。
最简单的方法就是将索引值限制在上限范围之内,也就是将比上限更亮的光线都用可能的最亮亮度等级来表示。另一种方法是对所有的颜色进行缩放,从而使得到的最亮颜色与可能的最大值对应起来,这样,其余的颜色亮度也就相应的减小了。
尽管非彩色光对许多技术仍然非常重要,但是现代的3-D图形已经开始将彩色作为提高视觉真实感的一个重要的工具了。
8.2.2 颜色模型的三个成分
我们已经认识到光线分布的某些特定属性对于我们的感觉有更重要的作用 。主波长影响着颜色的色调(hue),而光线的分布区域则影响着颜色的饱和度。能量分布的优势区域内的能量等级影响着颜色的亮度。这些概念都是很直观的,因此, 我们就有了一个模拟颜色的方法,那就是通过对颜色的色调(hue)、饱和度(saturation)以及亮度(brightness)进行描述。这就是我们常说的HSB系统,下图显示了HSB系统的颜色空间(见图8.3):
图8.3 HSB颜色空间 |
在这个系统中,色调用角度进行度量,亮度在水平方向上进行度量。这样,黑色就位于颜色空间的底部,白色则位于顶部。从图中我们可以看到,饱和度在亮度很小时其大小也是很小的,当亮度增大时,它的范围也相应增加了。
尽管这个系统在直观上非常地清晰明了,也允许我们对颜色做进一步的详细说明,但它仍然不适于在计算机程序内来使用。为此,我们仍然需要寻找一种系统能够很容易的表示几个光源之间的相互作用,很容易的表示多个光源的合成效果或者很容易的在两个已知颜色间进行内插运算。
既然我们已经知道人眼的敏感度集中在三个区域内,那么我们就可以尝试着用这三个区域内颜色的加权和来表示一个单独的颜色。这三个眼色就是红、绿、蓝,也就是我们常说的三原色。这样,我们就可以用下面的形式来说明一个颜色了:
式中的R、G、B分别表示红、绿、蓝三种颜色各自的亮度等级。这样我们只用三个值就可以表示一个颜色,当然要比用能量分布表示颜色的方法更容易操作。尽管这种方法并不完全正确,并且也不能覆盖所有的可见颜色范围,但是它确实十分简便,并且它的加和性(additive nature)也有很高的应用价值。
这种系统之所以具有加和性,主要是因为我们能够将纯色相加而产生处一个合成颜色。在第一章中,我们已经遇到过采用RGB系统来表示颜色的实际硬件。
由于这个系统的加和性,我们也可以使用矢量加法的形式将两个光线的效果进行合成,也就是将RGB三个分量分别相加:
从直观上来讲,我们可以认为每一种颜色都位于下图这样的一个三维空间中的一个点上,该空间的每一个轴都是一种基色:(见图8.4)
图8.4 RGB颜色空间 |
通常,我们也要限制每一种基色的亮度范围,这一点与非彩色光的情况相同。这样,所有可能的颜色就都位于图8.5所示的立方体内:
图8.5 颜色立方体 |
在这个立方体中,灰度的亮度位于对角线上,白色表示为(1,1,1)黑色表示为(0,0,0)。立方体所包围的空间完全对应与HSB系统所表示的锥体空间。如果我们沿着对角线将白色和黑色点连结起来,那么这条线就正好对应于图8.3中锥体的轴。
有一个问题与我们在使用非彩色光线时一样,那就是两个彩色光线叠加后的效果有可能会超出上图立方体所表示的范围。那么这时最普通的方法就是将超出范围的三种颜色成分分别钳制在规定的范围之内。还有一个方法就是对所有的颜色进行缩放以避免上述溢出情况的发生。由于大多数情况下我们无法预先知道所有光线可能的值也无法知道它们叠加之后情况会如何,所以我们通常还是采用第一种方法来处理溢出情况的发生。
除了上面提到的几种颜色模型之外,还有其它许多种模型来描述颜色,如:YIQ,它主要用于描述广播电视信号;CMY,它多用于hardcopy打印技术。我们在计算机图形学中主要使用RGB系统,这主要是因为它能将大多数的光照问题分解为三个相互独立的基色来处理。由于系统的加和性,对于每一种基色单独进行处理的方案进行合并之后所得到的效果与我们在真实世界中所看到的效果是完全相对应的。例如,当我们要对两个颜色进行内插时,通过对两个颜色的三个成分分别进行内插,然后将它们合成起来得到的效果与直接进行内插得到的效果是一样的。而在这种情况下使用HSB系统有可能无法得到满意的结果。我们通常会给RGB系统添加第4个分量“alpha”,它用来描述透明属性。这种扩展的RGBA系统有助于我们对复杂照明效果进行模拟。
在第一章中,我们已经介绍过显示硬件可以为描述图象位图提供不同方式的支持。其中有一种是像素的颜色直接通过RGB值来进行描述,另一种则是通过一个调色板索引来进行描述。在后一种方式中,调色板中存储了实际的RGB值,并且调色板的大小也有限制。我们经常要将第一种方式存储的位图转换为后一种方式存储的位图,这时就要被迫减少颜色的数目。由于第一种表示方法是连续的,而第二种方法是离散的,因此这一问题就是我们所说的颜色量化(color quantization)。
如果我们要从原始图象中选择所有唯一的颜色,我们就必须在颜色空间中找到一些簇(cluster),这样在一个簇内,颜色间的距离是最近的,然后我们用一个单独的颜色来代替整个簇。直接解决这一问题的计算量是很大的。但是我们已经有了许多技术可以有效的找到一些近似值,这样就产生了我们感觉上可以接收的颜色简化(color reduction)。下面我们要讨论其中的一种叫做median-cut(中线切割)的算法,它最初于80年代早期由P.S. Heckbert提出。
这种算法将颜色视为三维空间中的点来看待,它首先要找到围绕最初颜色的边界盒,然后沿着最长一边的中线(median)将边界盒切割为两部分, 这样就可以将问题分解为两个子问题来解决。图8.6这时了一个平面例子的处理步骤,它忽略了B-轴。
图8.6 颜色简化过程的步骤 |
如图8.6(a)所示,我们沿着边界正方形的最长一条边将它分为两个部分。然后依此类推计算得到的两部分中颜色的边界正方形,并进行分隔(如图8.6(b)所示)。我们可以看到,一个分隔沿G-轴进行,另一个分隔则沿着R-轴进行。一旦我们达到了所需的数量要求,我们就可以停止进行分隔。例如,如果我们要用四种颜色来代替原先的颜色,那么经过两级分隔之后就可以不再进行分隔,接下来就要在每一个部分中分别计算颜色点集合的质心,从而得到我们所要的颜色(如图8.6(c)所示)。
在进行分隔时有一点要注意,那就是为了提高颜色简化的质量,还要考虑到原始颜色的点在图象中出现的频率问题。这一信息在计算中线以及质心时要予以考虑。该信息在中线和质心计算期间使用,这样,沿轴的中线被作为沿轴的坐标被这些频率总和分割开的频率次数和来计算。质心的坐标被作为各自沿轴的中线来计算。
在下面的讨论中我们将会看到,为了很好的表现出照明效果,我们要求能够表现出同一颜色不同的亮度等级。当图象位图存储了整个RGB值的时候,这一点并不难做到, 但是如果我们使用调色板来进行描述时就要变得复杂的多了。这时,我们只有有限的几种颜色可用,并且位图存储的是调色板入口的索引值。这时,为了对应于某些更亮的颜色,可能就要调整颜色板中的颜色以便增加索引值。但是,如果调色板中某些颜色的色调完全不同的话,那么以任何顺序来安排它们都是十分困难的。有时,我们可能要在逻辑上将调色板分割成一些颜色组,这样在这些组中,增加的索引就能与更亮的亮度等级相对应。
必须要注意的是,这些组可能仍然会共用某些颜色。例如,任何在零照明情况下的颜色都支持黑色。既然我们要考虑颜色数目的限制,那我们就不能允许重复存储同样的颜色,这样就有必要用另外一个调色板来反映这些共用的颜色。为了表现出共用的颜色,我们可以建立一个二维数组,用一维来表示颜色的索引,另一维来表示亮度等级的数目。由于有颜色共用的存在,表格的大小可能会超过调色板的大小。
还应强调的是,增加的索引完全对应于使用非彩色光时的情况。如果我们要使用不同颜色的光源,那就有可能在第二个表格中需要更多的维数。
8.3、照明模拟
在前面的讨论中,我们已经研究了光线的本质、属性以及在计算机图形中模拟不同颜色光线的一些方法。然而,由一些光源以及我们周围物体所发出的光线是有相互作用的。
三维图形的最终目的就是要描绘出来自于真实世界的图象。为了这一目的,我们当然要重视对人类视觉有重要作用的光线与物体的相互作用问题。
在光线与物质之间有许多种类的相互作用。例如,物质可以吸收、反射或者是传播光线。这些效果都可以用光线的粒子属性来进行解释和模拟,也就是说,可以把光线模拟作一束很小的粒子。模拟其它的效果,如干涉、衍射或者是反射,则需要用光的波动理论来进行解释。总之,充分考虑到光线与物质相互作用的光照模型是很复杂的,同时用计算机处理起来计算量也很大,特别是要在具有交互性的程序中实现计算量就更大了。因此,大多数程序就只去考虑一些绝对重要的相互作用,如比较显著的反射效果。在这一部分中,我们将讨论在计算机图形学中广泛使用的光照模型的一些组成部分。
8.3.1 环境照明
在计算机图形学程序中,最简单并且也是最早应用的照明模型恐怕就是环境光模型了。在这个模型中,我们假设只有很少的光源,且其照明在任何方向都是相等的。
使用这种照明方式,每一个表面所表现出的都是它固有的反射能力。能够较好反射光线的物体看起来就会亮一些,而吸收了大部分光线的物体看起来则会暗一些。在日常生活中,我们会发现物体经常都处在一个由太阳发出的统一的白光照射下。很明显,这与我们通常所说的有材质物体的颜色有关。我们所说的某种有材质物体的颜色实际就是描述了它对白色光的反射能力。通常,反射能力可以用[0,1]范围内的一个系数来进行描述,0意味着物体将百分之百的吸收一个给定频率的光线,1则表示完全反射。我们可以看到, 这个系数是与波长有关的 — 大多数物体会吸收某些频率的光线而反射掉其它频率的光线。因此,一个有材质物体的反射能力就可以用一个分布来进行描述,在这个分布中,反射系数是入射光线波长的函数。根据前面对颜色模型的讨论,我们知道一个颜色既可以用一个索引值来进行表示(非彩色光情况下),也可以用三基色来进行表示(彩色光情况下)。这样,我们所说的物质的反射能力也就既可以用一个单独的系数来表示,也可以用三个系数同时来表示。总的来说,我们用下面这个简单的公式来表示光照模型:
使用上面的公式,我们可以得到在环境光照明情况下光线反射后的亮度值。注意,公式中的和在非彩色光模型下均为表亮,在彩色光模型下均为三维矢量。
由于环境光等级保持为常量,这样我们就可以预先设置场景中每个表面的使每个物体都能使用它本身的颜色。下图中的物体使用了环境光照明模型,我们可以看到,它的真实感是很差的,我们只能从图中看到物体的轮廓而已。
图8.7 环境照明 |
为了提高图象的真实感,我们就需要寻找更复杂的照明模型。但是这种古老的方法至今仍然在广泛使用,特别是用来执行一些我们不愿去计算的效果,例如光线在物体表面的多重反射。
8.3.2 漫反射
在真实世界中,我们经常见到的光线都是由某些固定的光源发出的,它们总是从某一个方向照射到物体上,而不像我们在讨论环境光时那样不用考虑光线的方向。为了模拟这种情况的反射效果,我们在考虑反射表面属性的同时,还要考虑光源的位置和类型。我们将光源分为点光源、聚光源以及方向光。正是这些光源在真实世界中照亮着各种有材质的物体。漫反射模型就是指物体表面把这些光线均等的反射到各个方向(如图8.8所时)。
图8.8 漫反射 |
这一个模型主要是针对粗糙表面而建立的。如上图所示的粗糙表面,表面上每一个小区域内都有大量的朝向各个方向的小的表面,因此它的反射也是朝向任意方向的。这样,在任何方向上反射后的光线的亮度就只依赖于有多少光线照射到表面上。当然,它是光源与表面之间方向的函数。如果表面朝向光源方向,与灯光的方向正交,那么反射的光线亮度是最大的。
图8.9 漫反射图示 |
下面我们来看漫反射公式:
从公式我们可以看出,当入射光线与反射表面正交时,反射达到最大值,并随着入射光线与反射面夹角的减小而逐渐减小。同时要注意,当角度大于或者小于时,此光源的光线没有照射到该表面上,因此也就没有反射。但是如果只从公式来看,我们得到的值将是负值,这是违反物理常识的,因此要将它设为0。
从实际情况来考虑,上面的情况也会导致一个问题。当环境中只有一个直射光源时,那么所有背向它的表面都将是黑暗无光的。但是在实际情况下,总会有一些光线通过多次反射能够到达上述表面。而我们使用的这个漫反射模型只考虑了由光源发出的光线,没有考虑到经过其它表面反射的光线。在实际应用中,我们对上述模型要进行一定的调整,用环境光来模拟多次反射的情况:
得到修正后的模型称为Bouknight照明模型, 是根据它的发明者来命名的。图8.10显示了使用这个模型来进行渲染所得到的效果:
图8.10 漫反射或Bouknight照明 |
在使用这个模型时,假设已经知道了表面和光线的属性,但仍然需要有一个方法来计算值。在前面的章节中,我们已经认识到了法向量在描述一个平面的方向时的作用。那么现在,让我们用一个单位向量来表示光源的方向。根据定义,两个矢量的标量积(scalar product)公式如下:
式中和是矢量的模。假设上述两个矢量都是单位长度的,那么我们就可以套用上面的公式,用光源方向矢量和需要计算反射光亮度的那一点处的面法向量来计算值。这样, Bouknight照明模型就可以用下式来代替
:
公式中,根据我们使用的颜色方案的不同,各项可以是标量(非彩色光情况下),或者是三维矢量(RGB颜色模式下)。
在前面几章中,我们已经介绍了法向量的计算方法。但是,直到目前为止,我们还从来没有要求过法向量必须是单位长度的。实际上,在进行背面剔除(back-face culling)或者是建立平面公式时, 矢量的长度是没有特别要求的。但是在进行照明模拟时,我们就需要用到单位长度的矢量。很明显,我们只要用矢量除以它当前的长度就能使它单位化了:
根据定义,一个矢量与它本身的标量积就等于它的长度的平方。这样,将矢量与它本身的标量积开平方就可以得到该矢量的长度:
在计算单位矢量的过程中,唯一比较困难的地方就是计算平方根了。它的计算量是比较大的。
有一个快速的计算平方根的方法,称为二分搜索算法(binary search algorithm)。在这个方法中,我们先假设一个结果,然后计算它的平方。如果平方值比我们讨论的值大,那么我们假设的平方根也就要比真实值大。相反,如果平方值比我们讨论的值小,那么我们假设的值也就比真实值小。这样,我们就可以使用二分搜索技术,每次将两个假设值之间的距离减小一半,直到它们的距离小于精度要求时,我们也就得到了满足精度要求的平方根值。尽管这种方法是比较快的,并且只是一些迭代过程,但是它在每一步迭代时仍然要进行平方运算。在许多情况下,空间中物体的坐标值都是整数的,并且在矢量长度的计算过程中,我们也希望结果能是整数的。这样,我们就可以充分利用上述算法中的假设来分析应该设置哪些结果位。
利用上述算法,我们先估计一下结果可能的最高位,然后为最高位假设一个值,并计算它的平方。由于只设置一个位,因此它的值就应该是2的幂,这样,就可以通过移位的方法来进行平方运算。得到平方值之后,如果它比我们讨论的值大,那么这一位就不是最终结果中的位,相反,如果小于讨论值,那么它就是最终结果中的位。接下来设置次高位。由于这一步中的估计值不止一个位,因此不能使用移位的方法来进行平方运算。我们可以用下面的公式来计算平方值:
如果a是第一步得到的估计值,b是当前位的假设值,那么(a+b)就是当前的假设值。这个新的假设值由三个部分之和组成。第一部分是上一步迭代中的平方值, 后面两部分都是两个2的幂相乘得到的值,这样,这些部分都可以通过移位来进行计算。得到平方值之后,检查这一位是否是结果中的位。然后我们要处理下一步迭代,同样,我们会遇到与上一步一样的情况,我们可以按照相同的方法来处理,这样就能得到这一步的结果。以此类推,直到计算完所有可能的位之后,我们也就得到了平方根的整数近似值。程序清单8.1列出了这一算法的实现:
程序清单8.1 按位迭代计算平方根 |
要注意,在表7.1所列出的代码中我们认为整数占据了32位,因此,我们在估计最高位时,要保证它的平方值不会超过可能的精度。
8.3.3 镜面反射
在真实世界中,不仅要使用漫反射模型,同时还要接触大量镜面反射的情况。通过镜面反射,可以看到物体表面的高光,或者是光源在光亮物体表面上的反射。
图8.11 镜面反射 |
用来描述镜面反射的模型需要知道光源和观察者的位置,以及反射表面的朝向。当观察者位于一个合适的角度时,可以看到物体表面上某个地方的高光。当观察者移动时,可能还会看到从表面上其它地方反射的高光。对于大多数的材质来说,镜面反射时,光线的波长不会发生很大变化,因此高光的颜色与光源的颜色仍然是一样的,而不会根据反射表面的颜色发生变化。
通常,描述光亮物体的反射情况是很复杂的。在计算机图形学上,我们一般只使用一种描述镜面反射的模型,Phong照明模型,它是根据其发明者Phong Bui-Tuong来命名的。这一模型适合用来描述不太完美的反射表面的反射情况。当我们接近反射角度观察时,根据这一模型得到的镜面高光与光源的波长成分是一致的。当观察者从镜面反射的方向逐渐离开时,反射光线的亮度也逐渐减小(如图8.12所时)。
图8.12 镜面反射图示 |
亮度的减少量大约是,是一个标量系数,它表示了入射光线反射的百分率。由于反射光线的波长成分与光源的一样,因此对于任何颜色模型来说,这个系数都是标量。公式中的n表示了表面的光亮属性,它的范围可以从1到无穷。不太光滑的表面需要较小的指数,这样反射得到的高光亮度衰减就回大一些,高光也就会暗一些。相反的,一个比较理想的反射面所要求的指数就要很大,这样高光才会比较强烈。
对于非理想反射面来说,它的反射中既有镜面反射成分,也有漫反射成分。这样,就要对Phong照明模型进行一定的修改:
注意,我们仍然使用了环境光来模拟多次反射得到的效果。下图显示了上面模型的效果:
图8.13 Phong照明 |
在上面公式中,我们需要计算的值。我们可以使用前面提到的标量积的方法来计算它,这里要用到两个单位矢量,一个是表示镜面反射方向的,另一个是指向观察者方向的。用表量积的形式我们对上面的公式进行修改:
但是在这个公式中, 还要使用表示反射光线方向的矢量。在镜面反射模型的图示(图8.14)中我们可以找到它与其它角的相等关系。
图8.14 镜面反射图示 |
从图8.14中可以看到, 矢量是矢量和的和。矢量又是矢量在法线方向上的投影。投影的长度可以用来表示,可用标量积来进行计算。由于和都是单位长度的,所以我们可以用来表示矢量的长度。而本身就可以表示为。同样,我们用来表示矢量。这样,镜面反射的方向就可以用下是来进行表示了:
将上式代入Phong照明模型的表达式:
这个公式的计算量是很大的,特别是式中有大量的矢量点乘。在实际应用时,我们经常用一个不太严格的公式来替代它。当观察方向接近反射方向时我们不再进行测量,而选择在表面的朝向变得能够对观察者产生高光时进行测量,这时,我们就要考虑一个新的夹角,即表面法向量与半程矢量(half-way)间的夹角(图8.15)。
图8.15 镜面反射的替代公式 |
矢量刚好位于光源方向与指向观察者方向的中间方向上。这样,当它与法向量一致时,反射方向也就与指向观察者的方向一致了,我们就可以观察到镜面高光。这时,我们用下式来表示Phong照明模型:
使用这个公式,我们可以减少一些计算量,因为计算要比计算相对容易一些。还有一种减少计算量的方法,就是假设光源和观察者都位于无穷远处,这样半程矢量就会近似不变。但是,这样对于具有交互性的程序来说并不是一件好事,因为镜面高光将不会按我们设想的那样随着观察者的移动而发生变化。
我们还应注意到,这两个公式是不相等的,它们也都无法描述反射的真实物理过程。但是,既然我们的目的只是要表现出一个足够真实的虚拟场景的话,那我们也就只好接收这样的模型,因为它们的计算量是我们现在的设备所能够接受的。
对于这个模型,还有许多改进的方法。有一些模型加入了一些对非反射现象的考虑。例如可以考虑光源,还有大气衰减等。对于光源来说,由于我们使用的是点光源,因此在各个方向上的辐射能量都是相等的,并且形成了一个球面,这样,随着距离的增加,能量也将会随之衰减,并与距离的平方成反比。对于大气衰减来说也是一样,当光线穿过统一的介质时,有一部分会被吸收掉,因此亮度也会随着距离的增加而减小,并且与距离成反比。我们还可以在公式中考虑光线的传播问题,这对于模拟透明或半透明物体是非常有用的,例如水或者是玻璃。增加所有这些效果对于更加真实的描绘场景无疑是一件好事,但是我们强调一点,那就是各种效果的使用要通过对实际效果的试验比较而不是单纯的分析来决定。
8.4、在屏幕到世界(screen to world)中观察照明
迄今为止,我们只讨论了光源与被照亮物体之间的相互作用。然而在实际生活中,经常会有多个光源与多个反射物体,它们彼此之间以各种方式相互作用和影响。因此,处理整个虚拟场景的光线就要使用全局照明模型。在这一部分中,我们将讨论如何将一个全局照明模型添加到屏幕到世界以及光线投射过程(ray casting)。当我们以屏幕到世界用于计算可见性和照明时,这种算法经常被称为光线跟踪(ray-tracing),以便与仅应用于可见性判断的光线投射方法相区别。
在屏幕到世界中使用照明模型,它的方法是比较一致的。我们可以回想一下,在这种观察方法中,每一个像素将有一束光线投射到屏幕上,这样就可以来表现可见场景了。然后对所有分隔表面进行检查并选择离观察者最近的一个来进行显示,完成可见性的判决。
要增加照明信息,就要确定分隔表面的属性,例如它的法向量、材质等。这些属性与光源属性、位置、亮度等相互结合,向我们提供了应用照明模型的一些信息。在有多个光源时,它们的效果将会相互叠加。在前面的图8.7、8.10和8.13中,我们分别对环境光、Bouknight和Phong等区域照明模型分别使用了光线跟踪算法。
但是上述的区域光照模型只考虑了一个单独的对象和光源之间的关系,没有考虑由于多个物体的存在而产生的效果。例如,一个光源可能不能照射到某个点上,这样,在这个点上就不会产生阴影。同样,有一些物体可能不是由光源所照亮的,而是由一些反射的光线照亮的。
幸运的是,我们把应用于可见性判决的光线跟踪算法进行一些扩展,使它能够处理全局照明的问题。我们要考虑两种效果,第一种是在要进行光线照明模拟的点上检查光源的可见性。我们从这一点到光源投射一束光线,它叫做阴影射线(shadow ray),通过它我们来进行光源的可见性判决。如果这束光线在到达光源之前穿过了几个表面,那就表示光源被遮挡住了,也就不用考虑该光源在这一点的照明问题。下图显示了这一过程:
图8.16 计算阴影 |
要注意,我们要找的是位于光源和该点间的第一个交汇点。因此,在找到第一个交汇点之后,就可以停止检查。还有一点要注意,那就是不要考虑阴影射线位于光源和该点之间以外的部分。我们可以构造一个有方向的矢量来完成上述任务, 这个矢量是光源位置与给定点之间的差分。使用这个射线,位于该点之前的交叉点的参数t的值将会是负的,而位于光源之后的交叉点的t值将会大于1。
增加了对阴影的考虑之后,场景的真实感得到明显的增加。图8.17中展示了一个使用上述技术的场景:
图8.17 考虑阴影的光线跟踪 |
观察图8.17,我们可以看到,所有的阴影都很明显,刚好划分出了照亮区域和阴影区域。但在许多真实环境中,阴影与照亮区之间都有一个过渡区,这主要是由于真实世界中的光源并不是理想点光源所造成的,它们总有一定的面积。为了更加接近真实环境中的实际阴影效果,在进行计算时不仅要考虑是否能够照射到,还要考虑有多少光线照射到的问题。这样,位于阴影边缘区域的部分就会有一定的过渡效果。
我们已经注意到,一个点的照明不仅要考虑光源的直接照射效果,还要考虑反射光对该点的照明效果。这样,我们在考虑照明模型时就要将直接照明和反射光照明两部分都加入进去:
在使用通用的Phong照明模型和特殊的镜面反射时,我们可以使用一种被称为递归光线跟踪(recursive ray-tracing)的方法来找到上述的反射光线对光照模型的影响。在镜面反射情况下,总有一个主要的反射方向。以前,我们通过检查这个方向来判断一个点是否能产生镜面高光。在考虑反射光的情况下,我们还要通过检查这个方向来判断是否还有其他光线能够在这一点产生出镜面高光。这样,通过递归使用光线跟踪算法,我们投射出一个沿着反射光方向的射线,就可以完成反射光线对物体照明的判断。下面的图8.18显示了上述计算阴影以及反射光线的过程。
图8.18 计算阴影和环境反射 |
当然,这一过程是循环进行的。为了计算正确的光照路线,还要产生出其他的反射射线。在实际使用时,要对光线的反射次数进行一定的限制,不能无限次的反射下去,经过多次反射之后,有些光线就可以忽略不记了。
环境反射还要有一个系数来进行加权,因为物体的反射能力是有所区别的。尽管在实际生活中这一系数对于光源和反射光都是一样的,但我们通常还是将它们分开来进行考虑,这样可以对最终的图象结果进行更好的处理。图8.19是一个通过递归光线跟踪方法计算的场景的效果:
图8.19 考虑阴影和环境反射的光线跟踪效果 |
在图8.19中,反射的效果已经十分接近真实情况了。要注意一点,一些反射所反映的场景图象是我们所无法直接看到的。既要模拟虚拟世界中的额外物体,又要计算不能直接看到的物体间的光线作用效果是很耗费系统资源的。在这种情况下,我们可以将可见物体放置在一个立方体中,然后把用来描绘不可见环境的静态图象以纹理的形式贴附在它的内部多边形上。这样一来,既避免了对额外效果的模拟(因为纹理图象都来自真实环境),又减少了循环的次数。
通常,光线跟踪算法可以通过很简单的语句就能实现,它被编制成一个统一的框架,应用于不同的任务,如可见性判断、阴影、以及光照模型的其他部分。程序清单8.2中列出了一个主跟踪路径,用于进行当前的渲染。
程序清单8.2 递归光线跟踪算法 |
我们还可以将光线跟踪算法进行扩展,进行其他光照效果的的计算,例如穿过半透明物体的光线反射。
我们还可以给上面的算法加入一个计算光线亮度随距离衰减的函数,这样就可以考虑光线衰减问题了。我们还经常要对表面的无规律则性进行模拟。这时,可以给表面分配一个纹理图样,使它的每一部分都有不同的反射系数。为了更好的对表面的无规律性进行模拟,还可以使用凹凸贴图(bump-map)。在使用它时,物体表面的法向量是杂乱无章的,因此反射也就有不同的方向。
我们可以将以上这些效果以及其它可能的效果混合在光线跟踪框架中,这样产生出来的效果会十分逼真。
但是,使用递归光线跟踪方法时也会有一些问题出现,它们直接导致了在对一幅光线跟踪图象进行观察时会出现一些问题。首先,我们在处理镜面反射时要依赖于不同的光线属性。在光源方面,我们通常设置一个近似的函数来产生较强的光线。另一方面,环境的影响则通过反射射线来进行计算。所有由光源产生的高光的大小最多只能有一个像素。换句话说,我们在由光源产生高光的情况下对表面的不理想性进行调整,而不是在由反射光线产生高光时进行调整。结果,反射的效果往往就超出了实际的情况,变得非常的好。
在使用修正的Phong照明模型来计算环境反射的影响时,我们还会遇到另一个基本的问题, 那就是不用考虑漫反射光线对环境反射的影响。所有的漫反射表面反射的光线都会发散到所有的方向,因此总会有一些光线照射到使用照明模型的点上。对于这些光线,我们的模型将不予考虑。既然漫反射的光线可以照射到一些给定的点上,那我们就应该对所有方向都发射出一束射线来检测是否对光照效果有作用。由于这一过程也是一个循环进行的过程,因此在计算上是非常难以实现的。在下面的讨论中,我们将主要考虑解决多个漫反射表面的照明问题。
综上所述,光线跟踪算法是计算机图形学中一个十分重要的方法,它对计算量的要求使得它在一些有较多交互性要求的任务中的可用性大大降低。然而当我们将场景视觉效果放在首要位置,而又没有交互性存在时,这种方法是非常有效的。
8.5、辐射度
在前面的讨论中,光线跟踪通常都忽略了漫反射表面的影响,或者有时又太精确了一些,而我们通常又都使用一个环境光常量来对这种影响进行补充。然而,在许多实际情况下,这些影响又往往是非常重要的。例如,考虑图8.20中一个完全的漫反射表面B。它的方向背对着图中唯一的一个光源,这样, 根据漫反射照明模型,除非是有环境光存在,否则它将是漆黑的。在表面B的旁边还有一个漫反射表面A,而在实际情况下,A反射的光线总会有一些照射到表面B上,从而将它照亮。
图8.20 漫反射表面对照明的作用 |
为了解决这一问题,计算机图形学借鉴了热转移理论的一些内容。它的基本思想是,在一个靠近的环境中,能量分布是平衡的。现在我们假定有一个表面,它既能发出光线又能反射光线,由一个小面片发出的光线的量我们称之为面片的辐射度(radiosity),它等于面片本身发出的光线加上由其他面片反射的光线。可以用下式来表示:
公式中,B表示面片的辐射通量密度,E是它的辐射强度,K表示反射能力。这样,当场景由一系列的小面片组成时,我们假定面片的大小足够的小,使得穿过面片的辐射度为一个常量,那么上面的公式就可以用下面的公式来代替:
公式中,是一个系数,它表示有多少光线从面片j到达面片i。正如我们所看到的,辐射度方法允许我们对光源和反射表面进行统一的处理。一个为非0值的面片就是一个光源。系数被称为构成因子(form factor),它们本质上表示了场景中的几何体。为了计算构成因子,我们必须要考虑两个面片的方向,它们的范围以及它们是否被其他面片所遮挡。这是一个十分复杂的几何问题,但是一旦计算出了构成因子,场景中每一个面片的辐射度都可以通过解一个线性方程式来得到。将所有未知的辐射度B组合在一起,我们可以得到下面的线性等式:
上面的公式写成矩阵形式如下:
要注意,如果使用了单色模型,那么上面公式中的每一个元素都是标量,如果使用的是RGB模型,那么每一个元素都是三维矢量,也就是实际上有三个矩阵等式,分别对应于每个颜色分量。通过解上面的等式,我们可以得到每个表面面片的辐射度,接下来,在绘制每一个面片时,我们就可以根据辐射度数值来进行场景的可见性处理。
还有一点要注意,那就是我们假设通过每一个面片的辐射度为一个常量,这实际上是放宽了对条件限制。这种对条件的放宽,它的影响只有在面片足够小时才可以被忽略。这样,每一个实际的场景都必须要分割成许多小的部分,也就增加了矩阵的大小,当然也增加了求解的时间。但是从另一个方面来考虑,辐射度仅仅考虑了所有方向上的漫反射,并且它对于观察者的位置是没有约束的。因此,当场景中的几何体和灯光都是静态的时,我们可以再次使用同样的辐射度方法来进行计算。
总之,辐射度方法对于提高场景的真实感是非常有用的。但是,在定义中我们可以看到,它没有考虑到镜面反射问题,并且也很难在辐射度方法中加入对它们的计算。通常,我们都将辐射度方法作为光线跟踪算法之前进行的一个预处理过程。而这样做也并不是没有价值的,因为某些镜面高光正是由漫反射产生的,而镜面反射也可能导致漫反射的产生。由于在这两个处理过程中实现所有的效果时不现实的,因此许多实际的应用往往只限于一些普通的效果。
8.6、在世界到屏幕(world to screen)中观察照明中观察照明
在世界到屏幕中观察照明与在屏幕到世界中观察照明是很不一样的,它们的效果往往会大打折扣。让我们回顾一下我们所使用的方法,将图元投影到屏幕空间来创建一幅虚拟世界的图象,然后将它们光栅化为图象位图,这样我们就得到了一幅虚拟世界的图像。在进行光线跟踪处理时,我们将照明模型应用与世界中每一个可见的点上。在世界到屏幕中观察照明也可以采用类似的方法。当我们在将一个点放置在图象中之前对一个图元进行光栅化处理时,我们可以选择使用照明模型从而调整像素的颜色。有一些问题会立即显现出来。一般的世界到屏幕算法以及一些特殊的光栅化算法都要求一定的速度和相应的帧速。这些算法都将处理过程定位在对单个像素的处理上。通过对每个像素进行照明计算,提高了计算的复杂性。第二,在使用从后到前(back to front)光栅化处理方法来计算隐藏表面时,我们可能会对某些像素重新绘制很多此,这样,大量的计算量就耗费在了一些无用的像素上。在这方面,有一些隐面消除算法已经试图去避免这些情况的发生。但是,基本的问题仍然是按照每个像素进行光栅化,这样的计算量还是很大,我们还是需要考虑全局照明效果。
处理局部照明的一般方法是只对图元的几个特殊地方进行照明的计算,而在其它地方进行内插运算,这就叫做图元的明暗处理。我们已经在第三章中看到了使用内插进行明暗处理的多边形。对于一些全局照明效果,例如阴影和环境反射,在世界到屏幕进行观察不会在通用的框架中对它们进行处理,这些都要进行特别的处理才能达到,或者不予考虑。
让我们先来考虑一些明暗处理算法,之后,我们主要将讨论用于计算全局照明效果的一些技术。
图8.21 平面明暗处理 |
很明显,明暗处理的类型依赖于所使用的照明模型以及我们准备对一些条件所采取的放宽程度。例如,如果我们假设对多边形采取统一的照明,我们就可以对每一个多边形只计算一个点,然后使用得到的颜色进行光栅化处理。这一过程我们成为环境或平面明暗处理(ambient or flat shading)(见图8.21)。在环境照明情况下,上述假设一般都可以实现,但是在漫反射表面和Bouknight照明模型情况下上述假设只能在多边形是一个平面的情况下在能够实现,也就是说,对于曲面它是没有用的,并且在场景中只有方向光(所有的光源都有一定的作用距离)也是没有用的。让我们回忆一下这个照明模型的公式:
照明依赖于表面法向量()和指向光源的方向矢量()。当我们用多边形来对曲面进行近似时,法向量不再是一个常量(见图8.19)。如果又一个点光源的话,那么在多边形上指向光源的方向也会发生变化。在这种情况下,如果我们还是假设照明是一个常量的话,就会导致错误的结果。但是,由于计算机图形学是对真实环境的一种再现过程,而不是简单的进行复制,那么我们当然可以使用这种有一定失真的结果。
这样,即使在Bouknight和Phong照明模型情况下,我们仍然可以决定使用环境明暗处理。在后一种情况时,如果我们碰巧在镜面高光的位置进行了计算,那么整个多边形都将是高光,这样就会严重失真。
使用平面明暗处理方式的多面体由于人眼的视觉特性会出现一种马赫带效应,我们可以在图8.21中清楚的看到。尽管每一个多边形都有统一的颜色,但是在多边形边缘的地方,我们感觉到较暗的一边会变得更暗,而较亮的地方会变得更亮。当亮度有限时,人眼在接收光线时会对相邻的东西有所抑制。这样,对较暗一边光线的接收就会被相邻的较亮的一边所抑制,因此就会显得更暗了。而对较亮一边光线的接收没有被它的近邻所抑制,因此有较亮的信号响应。对于平面明暗处理,就会出现比较明显的轮廓,这并不是我们所希望的。
因此,我们还有另外一种方法来提高画面质量,它就是Gouraud明暗处理。这种方法对多边形的顶点计算照明效果,然后对亮度进行内插。图8.22显示了这种方法:
图8.22:Gouraud明暗处理 |
这种明暗处理对于描述漫反射的Bouknight照明模型同样适用。通过对亮度值进行平滑,它可以将曲面近似时出现的小块现象避免掉。
为了计算照明效果,我们首先要找到顶点的法向量。当我们用一些小平面对曲面进行近似时,顶点的法向量可以通过对小平面的法向量求平均的方法来获得,如下图所示:
图8.23 使用平面多边形来近似曲面 |
这种明暗处理方法还允许我们模拟漫反射表面在点光源不均允照明情况下的效果。但是,它并不适合于镜面反射和Phong照明模型。根据定义,内插明暗处理方法只允许在多边形表面对亮度进行线性的改变。而镜面反射的亮度变化却是非线性的:
这样,当镜面反射出现在多边形内部时,我们就会将它完全忽略掉,或者当高光出现在多边形的顶点处时,使用线性内插就会得到错误的结果。和环境明暗处理时的情况类似,当多边形较小或者对品质的要求较低时,这种方法还是可行的。
当我们需要正确的描绘镜面反射效果时,我们就要使用Phong明暗处理方法(不要与Phong照明模型相混淆)。只用这种算法,可以有效的在每一个光栅像素计算照明模型并对模型的一个成分在多边形上进行内插。让我们再看一下上面的照明模型公式。我们可以看到,计算时要用到法向量,这样,由于我们经常使用多边形来近似曲面,就可以对多边形的方向量进行内插,然后用它来计算每一点的照明。(见图8.24 (c))。
图8.24 (a)环境、(b)Gouraud及(c)Phong明暗处理 |
法向量的每一个成分被单独的进行内插,这样就会得到多边形上每个点的近似方向亮。我们必须注意,在计算时使用单位法向量是很重要的。在进行内插时,也要将每一点的法向量进行归一化。但是,在每一点都使用内插法向量来计算照明,计算量还是很大的。然而,正如在我们第一次考虑照明模型时所看到的那样,可以使用半程矢量来简化计算并将观察者的位置置于无穷远处。我们可以预先计算好(或者是替代公式中的),然后在执行时用表格来进行查询。
前面介绍的明暗处理方法允许我们在世界到屏幕观察框架中引入照明模型。但是,场景中多个物体的出现可能会要用到其它的一些照明效果,如阴影、环境反射等。我们还需要解决前面讨论过的漫反射表面的相互照明问题。辐射度算法可以用于对世界到屏幕进行预先处理。当场景中的几何体和照明不随帧的变化而变化时,它还可以在多个帧中多次使用,例如我们要产生一个飞跃的虚拟场景时。通常,当要求场景中的物体有一定的交互性时,在运行期间计算其它全局照明效果将很难实现,因此就需要对它们进行预先的处理。
在所有的全局照明效果中,我们将只讨论阴影和环境反射。计算阴影有许多种算法。我们通常将它们分为两组。第一组预先计算描绘阴影所需的几何信息。第二组在场景光栅化时计算动态阴影效果。因此,前一组算法是不能计算变化场景的动态效果的。
有两种方法来预先计算阴影。最常用的一个是将场景细分为许多部分,使得对于每一个点光源每个图元都能被完全照亮或者完全隐藏。另一种方法在纹理中存储阴影信息。后一种方法需要对每一个图元都设置一幅特殊的纹理,因此在有些情况下时不可行的。
预先计算阴影,我们就要解决可见性问题。在光源处可见的图元部分是可以被照亮的,而其它部分就应该有阴影存在。图8.25描绘了一个阴影域。
图8.25 阴影域 |
如图中所示,被照亮的多边形在空间中产生了一定的阴影范围。这个范围形成了一个多面体,阴影有它的顶部开始产生,位于这个多面体内的多边形部分都是有阴影的。而其它的多边形都可以被这个多面体裁剪为两个部分,一部分完全是阴影,另一部分则完全被照亮。被照亮的多边形还要标明是被哪个光源照亮的,这样在进行光栅化处理时,就可以明确知道每个多边形是由哪个光源照亮的。
这种划分是非常复杂的,但是我们可以利用合成场景的一些特性来减少计算量。例如,一个多边形往往都是一个物体中的一部分,这样,我们就可以直接对这个物体使用阴影范围来进行分割。减少阴影范围的数量也可以减少处理量。我们还必须明白,这种方法只是运行前的一个预处理步骤。这样,减少分割的数量就要比性能更重要。
使用BSP树算法来计算阴影范围是很有效的。使用BSP树,我们可以很容易得到按照从后到前顺序排列的多边形。同样,也很容易得到从前到后的顺序。我们必须在循环调用时将顺序翻转过来。对于某个光源的一个从前到后的顺序显示了哪个多边形能够为其它多边形产生阴影。很明显,在前面的多边形会对后面的多边形产生阴影,这样,列表中的多边形会对它后面的多边形产生阴影,但是不会影响到其它分支的多边形。我们可以使用阴影范围算法并沿着列表执行必要的分割。当我们将任何一个多边形分为阴影区和照亮区之后,所有的这些区域仍然属于同一个平面,并且可以被联合存储在树中原先的地方。
其它产生阴影的算法也包括了解决可见性的问题 。阴影Z-buffer算法(shadow Z-buffer)是对一般隐面消除算法的扩展。见图8.26。
图8.26 阴影Z-buffer |
在图8.26中,有两个Z-buffer,其中一个称为光线Z-buffer。如果我们在渲染场景之前计算光线Z-buffer,我们可以调整真正的基于光栅的Z-buffer使它能够解决阴影。当我们要将一个点放置在屏幕上和Z-buffer中时,我们可以找到它在光源前面的平面上的投影,并检查这个投影的Z坐标是否是存储在光线Z-buffer中的那个。如果真是这样的话,这个点对于光源就是可见的, 我们就可以在主图象中对这个进行照明。例如在图8.26中的点A。当我们检查它对光源的可见性时,我们在光线Z-buffer中定位一个比遮挡点的Z坐标较小的位置。这样,点A就有阴影。另一方面,点B就不是这样,我们在对它进行光栅化时就要考虑它的照明问题。
这个算法的一个明显的缺点是要对每一个光源都定义一个光线Z-buffer。同时,将额外的处理过程引入光栅化的内部循环中,提高了程序的复杂性,但是由于自身的特性,Z-buffer算法经常会覆盖掉一些像素先前的值。这样,我们可能会对一些不会在最终的图象中描绘出来的点来计算它的照明和阴影。解决这一问题的一个方法是先对图象进行光栅化处理然后加入阴影计算。这样,我们就只对出现在最终图象中的点才计算它的阴影信息。还必须注意,由于照明模型是附加的,我们可以对所有的光源暗中使用同一个光线Z-buffer。
在光线跟踪部分,我们已经讨论过使用正规的光线跟踪框架来解决环境镜面反射问题。本质上,我们在镜面反射的方向上解决可见性问题从而找到由一个光亮表面产生的环境反射。同样,我们在一个不同的框架中也可以实现世界到屏幕的观察。见图8.27。
图8.27 计算环境映射 |
图8.27描绘了一个镜面反射面片。很明显,由于它的镜面反射能力,这个面片可以显现出环境的反射。因为反射方向与入射光线关于法向量成镜像关系,所以我们可以假想一个观察者(如图中所示),根据它来计算一个单独的图像,然后将这幅图象一纹理的形式应用到原始观察的处理过程中。
观察算法的多重引用是很耗费计算量的,因此,我们可以预先计算好环境反射并保存在纹理信息中。必须注意,一般的纹理映射是与观察无关的,而环境映射却是与观察有关系的。当观察方向改变时,我们就要调整纹理映射使它随之改变,这样才不会影响场景的真实感。这种方法在几何关系上不一定是正确的,但却能使我们感受到环境反射的效果。
在有些情况下,我们可能需要动态的并且几何关系也是正确的反射效果,比如场景中的一面镜子。这样,才能对场景中的镜子进行正确的模拟。(见图8.28)