Shadow Mapping和Shadow Ray是目前绝大多数游戏和实时渲染应用使用的阴影技术,Shadow Mapping 的原理在于从光源发出 Ray(通过光栅化方式实现)来记录物体深度,与场景片元的深度比较大小来判定片元是否被遮挡;而 Shadow Ray思路恰好相反,但更直观:从场景片元出发,向光源发射 Ray 来检测片元是否被遮挡。
两种方式各有利弊,前者的主要缺点在于Shadow Map的分辨率是有限,容易出现精度不足的情况;后者的主要缺点在于 Ray Tracing 的开销可能会很大。当然,也存在两者结合的混合方法。
在光栅化的算法中,基于Shadow Map实现的阴影算法是最常见的,所以本文主要讲解 Shadow Mapping的相关技术。本文主要从以下3个部分介绍Shadow Mapping技术。
- Shadow Mapping的基本原理和实现方法
- Shadow Map的采样方法
- Shadow Map的重采样方法
Shadow Mapping的基本原理和实现方法

光照射不到的地方就会产生阴影,这就是Shadow Mapping的基本思想,光源的Ray从光源出发,来检测视口中的片元是否被遮挡。

- 利用RTT相机,在光源位置,生成阴影贴图(通常为深度图),获得深度值depth0;
- 主相机渲染场景,将视口中片元的位置与光源模型视图矩阵和投影矩阵相乘,计算出该片元在光源空间中的深度值depth1;
- 比较depth0和depth1的大小,如果depth1 > depth0,则该片元处于阴影中,反之则该片元不在阴影中。
理想的情况下,我们的Shadow Map的分辨率足够大,会得到一个比较完善的阴影,但实际上,直接使用Shadow Map可能会在不应该出现阴影的位置出现一些黑白条纹相间的现象(称为阴影失真,Shadow Acne)或阴影脱离物体的现象(称为阴影悬浮,Peter Panning),由于Shadow Map是一个二维数组,离散的存储方式很难完全表示实际的几何信息。尤其当光照方向不垂直于平面时,遮挡深度的采样会和实际深度产生偏差(如图一个不受遮挡的几何平面,但黑色加粗部分却被Shadow Mapping方法认为是被遮挡的)。

通常情况下,直接给采样阴影深度加一个 偏移量 Bias(相当于把阴影深度往远处加,从而更不容易产生遮挡)来解决。

但是,偏移量Bias通常与入射光的角度有关,当Bias过小时,会出现阴影失真(Shadow Acne)现象,当Bias过大时,则会出现阴影悬浮(Peter Panning)现象。除此之外,阴影通常还存在锯齿的问题,因为阴影贴图的分辨率有限,并且在透视投影过程中,一个像素对应的片元数量会更多,有可能产生严重的锯齿现象。总体来说,以上的问题均来源于Shadow Map有限的分辨率。为了在计算资源与阴影效率中找到平衡,有效的提高阴影质量,在Shadow Map生成时的采样过程和Shadow Map应用时的重采样过程,涌现了一批成熟完善的算法,使3D应用程序能够在有限的Shadow Map分辨率下,实时渲染高质量的3D阴影。
Shadow Map的采样方法
利用RTT相机,在光源位置,生成阴影贴图时,通常可以采用以下三类方法:Fitting、Warp、Partition,使Shadow Map中存储更多的有效信息。
1. Fitting
通常情况下,方向光使用正交投影矩阵,点光源和聚光灯使用透视投影矩阵(点光源使用6个方向的透视投影,形成立方体贴图),Fitting即是通过调整Light的视椎体,使 Light 视锥体缩小到刚好能包住潜在的阴影遮挡体(PSC,Potential Shadow Caster)的过程。Fitting方法能够尽量保证阴影贴图紧密覆盖到可视区域,更大限度的利用阴影贴图的分辨率。

2. Warp
Fitting 方法虽然最大限度的利用了阴影贴图的分辨率,但是不保证阴影贴图像素与片元的一一对应的,由于视椎体投影的关系,离光源越近图像区域越小,离光源越远图像区域越大,导致靠近视椎体的近裁剪面的区域浪费了大量的像素。
在生成阴影贴图时,对图像进行一定改的扭曲变形(对于透视投影,一般使用梯形映射),使生成的阴影贴图更加匹配视锥体的形状,靠近视锥体的近裁剪面的区域利用了更多的像素,这一过程就是Warp。
除此之外,还有一些Warp算法,对深度值进行包装,如:将depth打包到RGBA中存储,减少显存带宽的占用,将远近关系更加凸显的指数函数等等。
const vec4 bitEnc = vec4(1., 255., 65025., 16581375.);
vec4 EncodeFloatRGBA(float v)
{
vec4 enc = fract(bitEnc * v);
enc -= enc.yzww * vec2(1. / 255., 0.).xxxy;
return enc;
}
vec2 WarpDepth(in float depth, in vec2 exponents)
{
float pos = exp(exponents.x * depth);
float neg = -exp(-exponents.y * depth);
return vec2(pos, neg);
}
3. Partition
一种最流行的 Partition 方法是 Z-Partition:沿着z轴将视锥体划分为多个子视锥体,然后对每个子视锥体单独计算阴影贴图。
主要代表方法有三个:CSM、Z-Partition 和 PSSM,其思路差不多,都是使用多张阴影贴图,最出名的应该是 CSM(cascaded shadow map)。Z-Partition是沿光源的方向划分多个子视椎体,然后分别单独生成阴影贴图,CSM是沿主相机的方向划分子视椎体,而PSSM应该算是CSM的一种具体实现方法(一种平行切分视椎体的方法)

Shadow Map的重采样方法
通过Shadow Mapping技术得到的阴影的边界呈现突变现象,称之为”硬阴影”,并且如果阴影贴图的分辨率不够,还会出现锯齿现象。而实际阴影是由本影和半影组成的,阴影边缘是强度渐变的,称之为“软阴影”。为了模拟软阴影,我们通常采用滤波(Filtering)的方法对Shadow Map进行重采样。常用见的方法分为三类:百分比近似滤波(PCF,percentage-closer filtering)、基于信号处理的滤波方法、基于统计分布的滤波方法。
对于阴影贴图的滤波,是先把片元的深度和阴影贴图对应点进行比较,得到离散的 0-1 值,再对这些 0-1 值进行滤波,用局部多个点来确定某一点的“阴影程度”,此时得到的值可以是 0-1之间的连续值。
- PCF(百分比近似滤波):常用的方法有:BOX滤波、双线性滤波、泊松圆盘滤波等。
-
基于信号处理的滤波方法:常用的方法有:卷积阴影贴图 (Convolution shadow map,CSM)、指数阴影贴图(ESM)等。
-
基于统计分布的滤波方法:常用的方法有:方差阴影贴图(VSM)、分层方差阴影贴图(LVSM)、指数方差阴影贴图(EVSM)等。
文章评论