7

MIT 6.837:Real-time Shadow 实时渲染阴影

 2 years ago
source link: https://ksmeow.moe/mit-6-837-real-time-shadow/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

MIT 6.837:Real-time Shadow 实时渲染阴影

在实时渲染中,全局光照有光照贴图来减少计算量,而阴影也是一个需要考虑的问题。除此之外,由于光源不总是理想点光源,有一定面积的光源会产生软阴影现象,如何实现软阴影也是一个需要研究的问题。

点光源会在物体之后形成一块光照无法照到的区域,这是物体的阴影。由于阴影中的任何一点都没有光线射入,其阴影的亮度是一致的。

58 - MIT 6.837:Real-time Shadow 实时渲染阴影

而对于有面积的光源来说,它照射的物体下,会分成三个区域:本影(完全不能被照亮)、半影(只被部分光源的面积照亮)和完全被照亮的区域。其中,半影部分中也有从最暗到最亮的过渡。

59 - MIT 6.837:Real-time Shadow 实时渲染阴影

当使用面光源照亮一个物体时,其后方形成的阴影边缘不再锐利,而是存在亮度缓慢过渡的半影区域,这就是所谓的“软阴影”现象。

60 - MIT 6.837:Real-time Shadow 实时渲染阴影

要实现这种软阴影,需要在每一个要渲染的像素上向光源面积上随机追踪多条光线,根据被遮挡的比例来确定其亮度。这将成倍地增加需要追踪光线的数量,因此可以考虑引入一些优化,例如可以不判断 tMin 条件,遇到一个遮挡物就停止追踪(但会引入一些错误被遮挡的情况);由于光线的分部相对集中,可以先测试之前光线的遮挡物体。

阴影的预处理方法

最简单的情况,对要生成阴影的平面做一次该物体的投影。无法处理自阴影,且很多时候阴影并不投射到平面上,效果不佳。

61 - MIT 6.837:Real-time Shadow 实时渲染阴影

投影贴图阴影

一个观察是,计算阴影与计算屏幕投影是一个类似的过程。屏幕投影时将可见的物体深度记录下来(Z 缓冲区),将其颜色作为像素值;而计算阴影时,则是从光源的角度取最近的物体,对应光线上更远的物体则被遮挡而产生阴影。

因此,我们可以从光源的视角看过去,得到某一几何体遮挡的区域,保存为贴图纹理,并将其按投影关系应用到该几何体背后的平面上。

62 1024x362 - MIT 6.837:Real-time Shadow 实时渲染阴影

这是一个比平面投影更泛用的方法,但需要确定遮挡物体和阴影投射物体,也无法处理自遮挡问题。除此之外,由于纹理本身是图片,也存在分辨率的问题,导致投影结果中出现锯齿。

这是一种和投影贴图不同的做法,注意它们之间的区别。

我们曾提到,可以利用阴影和投影的相似性转化为光源视角的观察,而投影贴图中在这个观察中得到了遮挡物在平面上的透视投影。另一种思路是借鉴 Z 缓冲区的做法,在贴图中保存场景中每个交点到光源的距离,而阴影在对应光线中距离更远的点处产生。

63 - MIT 6.837:Real-time Shadow 实时渲染阴影

利用这种思路,判断一个点是否在阴影里的计算变为,将物体与视线的交点转换到光源视角的坐标系中,再用透视投影变换找到阴影贴图中的像素,并确认交点的距离是否比贴图中的距离更远。

这种方法可以正确地处理自遮挡问题,但仍然无法解决分辨率导致的锯齿问题,且还会存在超出贴图区域和精度问题。

如果使用一张平面贴图来实现阴影贴图,其覆盖的区域是有限的,无法正确处理区域外的阴影计算。一种方法是使用球形贴图来代替平面贴图。

精度问题一向是图形学中可能遇到的棘手问题,在阴影贴图中主要出现在判断距离处,由于计算精度损失等,判断距离时最好加入一个偏移量 epsilon,但它的取值选取需要以具体情况而定。

63 1024x404 - MIT 6.837:Real-time Shadow 实时渲染阴影

另一问题是由于贴图分辨率有限,投影时对贴图进行了上采样,导致了锯齿问题,锯齿在投影角度小时会更加明显。一种方法是对上采样后的阴影进行滤波,滤波的对象是是否在阴影面积中,利用一个低通滤波器使阴影的边缘变得平滑,卷积核的大小越大,突变边缘的过渡段越宽。

可以结合阴影贴图与投影贴图实现类似投影灯的效果,如图所示。

64 1024x422 - MIT 6.837:Real-time Shadow 实时渲染阴影

阴影的实时方法

一种实现实时阴影的方法依赖阴影体积与模板缓冲区(stencil buffer)。

模板缓冲区是用来对像素做标记的缓冲区,其本质是一个二维整数数组。

一种模板缓冲区的用途是实现是实时镜面反射。在正常绘制结束后,将屏幕上处于镜面中的像素在模板中标记为 1,并在这些像素中绘制反射的几何体。

65 - MIT 6.837:Real-time Shadow 实时渲染阴影

而阴影体积是一种表示阴影的方法。在点光源下,被一个面遮挡的部分体积类似一个台体,例如上图中被四边形遮挡的体积为一个没有底的四棱台,被这 5 个平面包围的部分可以认为被遮挡。

测试一个点是否在阴影里的方法有多种。最简单的办法是测试是否在 5 个面包含的面里,这种方法需要表示出所有阴影体积,且需要对所有光源和阴影体积做计算,开销很大。

另一种方法利用 Z 缓冲区。在追踪光线时,直接在屏幕空间绘制阴影体的各个面,绘制朝相机的面时测试 Z 缓冲区,通过则使模板缓冲区加 1;绘制背对相机的面时则减 1。由于测试 Z 缓冲区时阴影中的面会遮挡阴影体的背面,这些面对应像素的模板缓冲区会保留加上的 1,而未被遮挡处取值会被减回 0,这样可以快速把阴影中的面找出,并只在那些未被遮挡的像素处计算光照。这一方法的伪代码如下

Initialize stencil buffer to 0
Draw scene with ambient light only
Turn off frame buffer & z-buffer updates
Draw front-facing shadow polygons
  If z-pass → increment counter
Draw back-facing shadow polygons
  If z-pass → decrement counter
Turn on frame buffer updates
Turn on lighting and redraw pixels with counter = 0
66 - MIT 6.837:Real-time Shadow 实时渲染阴影

这种方法无法处理相机在阴影体中的情况,如上图所示,未被遮挡的点模板值反而为 -1。可以通过先判断相机是否在阴影体中来修正模板值,或裁剪阴影体,但这些方法开销较大。

一种比较好的做法被称作 Z-Fail,它将深度测试通过改为失败,先渲染背面,失败则加 1;再渲染正面,失败则减 1。这样,阴影中的部分将保留背面的加 1,而阴影外的部分则依然被抵消。此外,这种方法能够正确处理相机在阴影内部及外部两种情况。

67 - MIT 6.837:Real-time Shadow 实时渲染阴影

这一方法相当于从相机的对向无穷远处往相机投射测试光线,并执行和 Z-Pass 方法类似的过程。在实际的计算中,无穷远是无法实现的,通常选择在远裁剪平面裁剪阴影体积,以避免阴影体积超出裁剪区域造成的问题。

阴影体积可以通过仅保留轮廓边缘(正投影与背投影结果中都包含的边缘)来减少阴影体的面数来优化。

使用阴影体积实现实施阴影也有一些缺点和限制,例如阴影体积本身是几何体,会增加场景的复杂度;阴影体面有时狭长,光栅化开销大;模板缓冲区的值范围有限制,复杂度的场景可能导致计数溢出;如需要使用轮廓边缘优化,则物体中间不可镂空等。

68 1024x465 - MIT 6.837:Real-time Shadow 实时渲染阴影

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK