实时阴影技术:Shadow Mapping

2025年8月3日 693点热度 7人点赞 0条评论

Shadow MappingShadow 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的基本原理和实现方法

%title插图%num

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

%title插图%num

  • 利用RTT相机,在光源位置,生成阴影贴图(通常为深度图),获得深度值depth0;
  • 主相机渲染场景,将视口中片元的位置与光源模型视图矩阵和投影矩阵相乘,计算出该片元在光源空间中的深度值depth1;
  • 比较depth0和depth1的大小,如果depth1 > depth0,则该片元处于阴影中,反之则该片元不在阴影中。

理想的情况下,我们的Shadow Map的分辨率足够大,会得到一个比较完善的阴影,但实际上,直接使用Shadow Map可能会在不应该出现阴影的位置出现一些黑白条纹相间的现象(称为阴影失真,Shadow Acne)或阴影脱离物体的现象(称为阴影悬浮,Peter Panning),由于Shadow Map是一个二维数组,离散的存储方式很难完全表示实际的几何信息。尤其当光照方向不垂直于平面时,遮挡深度的采样会和实际深度产生偏差(如图一个不受遮挡的几何平面,但黑色加粗部分却被Shadow Mapping方法认为是被遮挡的)。

%title插图%num

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

%title插图%num

但是,偏移量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方法能够尽量保证阴影贴图紧密覆盖到可视区域,更大限度的利用阴影贴图的分辨率。

%title插图%num

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的一种具体实现方法(一种平行切分视椎体的方法)

%title插图%num

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)等。

StackSnow

追风赶月莫停留,平芜尽处是春山。

文章评论