Chapter 7 – Shadows 阴影

如果想要创建写实的画面,阴影无疑是非常重要的。本章节中,我们将会着重学习计算阴影的基础原理,并且了解实时渲染中最为重要与常用的阴影算法。但是本章节并不会覆盖所有关于阴影的知识点,笔者在此为大家推荐了两本书,分别为Real-Time Shadows以及Shadow Algorithm Data Miner

本章节所使用的专业术语如下图所示,occluder(遮盖物)表示将会在receiver(接收者)上投影阴影的物体。精准光源(punctual light),例如那些没有区域或者空间限制的光源,只会生成完整的阴影区域,有时这一类阴影也被称为hard shadows。如果我们在场景中使用了区域光源或者体积光源,那么就会生成soft shadows。每一个阴影可以拥有一个完整的阴影区域,其被称为umbra;也拥有一个局部的阴影区域,其被成为penumbra。

Soft shadow相较于hard shadow,其边缘较为模糊。但是我们并不能通过模糊(blur)阴影边界来模拟soft shadow。如下图所示,正确的soft shadow,或者说,符合常理的soft shadow应该表现为,距离投影物体更近的区域,阴影会表现的更清晰。

阴影的umbra区域并不等于由精准光源所生成的hard shadow。相反,soft shadow的umbra区域会随着区域光或者体积光的尺寸增大而减小,甚至有可能会消失(只要光源的尺寸够大,且接收者与投影者的距离够远)。一般来说,soft shadow的效果更好,因为边缘处的半影效果,让玩家能够将游戏中的阴影当作真的阴影。hard shadow相较于soft shadow,不那么真实,但是其渲染速度更快。

相较于绘制“半影”效果,我们更应该绘制场景中的所有阴影。因为,如果没有阴影,场景的真实度会大大降低,而玩家将更难辨识三维空间。一般来说,绘制非精确的阴影远好于不绘制阴影,因为我们的眼睛很难正确地分辨出阴影的形状。

在之后的小节中,我们将学习简单的阴影,以及实时渲染并计算的阴影。第一小节我们将学习阴影中的一个特殊情况,其投射到了平面上。第二小节则会为大家介绍更为常用的阴影算法,例如,将阴影投射到随机表面。同时,我们也会学习hard shadow和soft shadow,以及一些阴影算法中的优化技术。

7.1 平面阴影

阴影中最为简单的例子就是在平面上投影阴影。本小节中我们将学习这一类阴影的算法。

7.1.1 投影阴影

为了投影阴影,我们需要对三维物体进行二次渲染,以得到一个二维的阴影。可以使用一个矩阵将物体的顶点投影到平面上。如下图所示,光源位于l,将要被投影的顶点位于v,投影后的顶点则位于p。首先我们假设阴影平面为y=0,之后再将次计算结果范例化至任何平面。

首先我们将顶点投影至x轴,如上图左侧所示:

我们以相同的方法求出z轴坐标:pz=(lyvz-lzyy)/(ly-vy),由于y轴的坐标为0。我们可以总结出一下投影矩阵M

因为Mv=p,所以M就是投影矩阵。

通常情况下,阴影投影的平面不是y=0而是π:n·x+d=0,如上图右侧所示。而我们的目标是找到一个矩阵,能够将顶点v投影至点p。也就是说,以l为原点的射线将会穿过顶点v并与平面π相交。因此,计算点p的等式为:

该等式还能转换为以下投影矩阵,其也满足Mv=p

为了绘制阴影,我们只需要将上述矩阵应用到物体的顶点,之后就能拍平面π上投射出阴影,再对阴影运用较暗的颜色并除去光照。实践中,我们需要避免投影的三角形位于物体表面的下方。其中一种方法是基于投影的表面,我们加一些偏移量,这样阴影三角形就能永远绘制在表面的“上方”。

更为安全的方法是,先绘制平面,之后再将z-buffer或者depth test关闭后再绘制投影的阴影三角形。这样阴影便会永远位于平面的上方,因为我们不会再比较两者的depth。

如果平面是有限的,例如,其是一个矩形,那么投影的阴影就有可能落在平面的外面,这显然是不能接受的。为了解决这一问题,我们可以使用stencil buffer。首先,将接受阴影的物体,例如,地面,绘制到场景中,并将其绘入stencil buffer。之后,禁用z-buffer,通过使用stencil buffer test,只将阴影会知道地面的区域。

另一种阴影算法是将阴影三角形会知道贴图中,被阴影投射的平面将使用该贴图。这种贴图是一种light map(光照映射),其模拟了物体的表面(我们将在第十一章中学习)。将投影的阴影绘制到贴图中,其能够支持在带有弧度的表面生成“半影”。这一技术的一个缺点是,贴图可能会被放大(magnify),这意味着一个纹理将会覆盖多个屏幕像素,这将会使阴影效果较差。如果阴影并不会每一帧都改变,例如,光源与投射阴影的物体的相对位置不会改变,那么上述贴图就能被一直使用。

所有投射阴影的物体都需要位于光源和阴影接收者(平面)之间。如果光源位于光源位于物体最上方的顶点的下方,如果我们依旧进行之前的投影,那么会生成错误的阴影。正确的阴影与错误的阴影如下图所示。

当然我们是能够避免上述情况的。在下一小节中,我们将会学习如何使用GPU管线来进行投影的剪裁。

7.1.2 Soft Shadows

我们也可以让投射阴影变为soft shadow。本文将会使用Heckbert与Herf所提出的算法来生成soft shadow。这一算法的目标是为地面生成一张soft shadow贴图。之后我们将会介绍一些效率更高但是精确度较低的方法。

当光源拥有区域或者体积的属性时就会产生soft shadow。为了模拟光的区域效果,一种方法是在光源的表面进行一定频率的采样,也就是放置一些精准光源。其中的每一个精准光源都会生成图像并累加到一个buffer中。而这些图像结果平均之后就生成了soft shadow。需要注意的是,理论上来说,任何用于生成hard shadow的算法都可以通过平均值来生成soft shadow,或者说生成半影。

Heckbert和Herf使用一种基于视锥体的方法来生成阴影。其原理是将光源当作观察者,或者说当成camera,而地面则形成了视锥体的远平面。视锥体需要足够宽阔以包含投射阴影的物体。

通过不断生成地面的贴图,最终可以得到soft shadow贴图。区域光源将在其表面进行采样,每一个采样点都会对地面图像进行着色,之后将投射阴影的物体投影到图像中。所有的这些图像将被累加并进行平均,之后便会得到地面的阴影贴图。

对区域光进行采样以生成soft shadow有一个问题,也就是多个精准光源的阴影会造成重叠。此外,精准光源的采样频率越高,soft shadow的效果越精确,但是其开销也会非常大。所以这一算法只能用来生成一张作为基准的soft shadow贴图,用来与其他soft shadow算法作比较以检测soft shadow的效果是不是正确。

更有效率的方法是使用卷积(convolution),例如,filter。对一个精准光源的hard shadow进行blur(模糊)操作可以得到近似soft shadow的效果,有时这一类的“假soft shadow”也能满足我们的需求。但是,如果物体与地面或者平面接触,程序化的blur操作所生成的soft shadow的效果则不那么令人满意。

此外,还有许多方法能够模拟出soft shadow。例如,Haines先投射出hard shadow,之后在使用梯度图来绘制阴影的边缘轮廓,而梯度图的原理是阴影的中心较暗,向外会变得越来越亮,以此模拟出半影。但是上述这两种soft shadow并不是“正确的”,不过从效率上说,它们的运行开销都远远小于使用一组精准光源来模拟区域光源。

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据