实时渲染-02:阴影贴图与软阴影
光源可见性
一个表面被相机看见,不代表它也被光源看见。直接照明中的阴影,要回答的是第二个问题:从表面点朝光源看,途中是否有其他表面挡住光?
阴影贴图 Shadow Map 先从光源方向记录最近的表面,再用这份深度判断其他点是否在它后面。它用一张二维图代替逐点与全部几何求交,适合光栅化管线。建图仍然要处理场景几何,但查询阶段只需位置变换与纹理访问,不必再遍历模型。
对理想点光源或平行方向光,一点能看见光源或被挡住,直接光的可见性 Visibility 可以记为 或 。之后将对应光源的着色贡献乘以该值,而不是把全部间接照明、发光项也统一清零。
两趟渲染
第一趟把光源当作观察位置,渲染一张深度纹理。每个纹素保留该方向最靠近光源的表面深度;颜色、BRDF 和相机最终看到的亮度暂时都不需要。
第二趟从相机渲染场景。对相机可见的表面点 ,再将它变换到光源的投影中,找到对应阴影纹素,比较两份光源深度。

白线表示相机的观察,黄色线表示光源方向。左下白点与光源记录的最近表面重合,因此重投影后的深度匹配,该点能得到直接光。

这里相机看见下方白点,但光源沿同一方向先遇到上方绿色物体。相机点重投影后的深度大于阴影图记录的深度,说明它位于遮挡物后面。比较的两份深度都从光源定义,不是拿相机到点的距离直接与光源到点的距离比较。
1 | 光源趟: |
移动物体可以每帧重新建图,不要求提前固定全部几何。一个光源的投影只覆盖相应范围;点光源若要覆盖所有方向,通常需要立方体等全向表示,不能用一个有限视锥代表四周所有光线。
深度约定
设点的世界齐次坐标为 ,光源视图和投影矩阵为 、,先得到
下面采用传统 OpenGL NDC 的 区间,并将近平面映射到深度 、远平面映射到 ,则
第一趟的深度纹理也必须使用同一套约定。采用 reversed-Z 或其他深度映射时,比较方向相应改变,不能直接照搬上式对应的“小于等于”判断。背向光源、裁剪范围外、 非法或 UV 越界的点,需要按光源覆盖范围与其他阴影图处理。
透视投影的深度通常不等于实际距离。设光源近远面距离为正值 ,正向线性深度为 ,传统透视深度可写为
深度测试可以统一比较 ;后面用相似三角形估计半影时,则要使用有几何长度意义的线性深度 。把投影深度直接当作光源与遮挡物的距离,会让软硬变化失真。
自遮挡
实际建图只在有限纹素位置存深度,查询却发生在表面任意位置。同一阴影纹素覆盖一片表面,其深度在离散记录中近似为一个值;如果表面倾斜,这片区域的真实深度一直变化,一部分点就可能被误判为在记录深度之后。

左侧地面出现大量条纹,右侧手绘图把表面和离散记录关系画出来:从光源看,每个格子覆盖的表面区间被一个深度代表,相机查询的位置却不一定正好位于该采样点。误差把同一表面的一部分判为“被自己挡住”,形成 Shadow Acne。
掠射角时,光线与表面接近平行,一个纹素覆盖的表面跨度大,局部深度误差更明显;正入射时通常较轻。深度精度、采样位置和变换计算也会影响结果,因此只提高分辨率不一定能完全消除自遮挡。
偏移与脱离
最常见的处理,是给比较加入深度偏移 Depth Bias。沿用近处深度小的约定,可以写成
是条件成立取 、否则取 的指示函数。偏移允许接收深度略大于记录值,减少离散误差带来的自遮挡。它是在控制比较阈值,与阴影滤波宽度是不同参数。
倾斜表面可能需要更大的偏移。工程中可使用常量项加深度斜率项,或由法线与光方向构造角度相关偏移;不同方法对分辨率、投影和几何尺度的依赖不同,需要一致的单位和上限。

左图地面条纹减少,但脚边的阴影与物体接触关系变弱。右图橙色区间表示被容差忽略的深度差:偏移过大时,原本真实的近距离遮挡也会被当作数值误差。这个问题称为阴影脱离 Detached Shadow,也常叫 Peter Panning。
调参需要同时观察表面条纹与接触关系,不能把“阴影中没有条纹”作为唯一正确标准。薄几何、低角度光和大滤波窗口往往需要更细致的处理。
第二深度
另一个思路是额外记录光线穿过封闭物体的深度信息,并在第一层表面与第二层之间选比较深度,例如
这类 Second-Depth Shadow Mapping 避开紧贴第一层表面的比较值,使真实前表面不容易落到阈值之后。

左图红线靠近物体的受光前表面,中图展示第二深度,右图把中点放到物体内部。若物体封闭且前后表面对应合理,这能给前表面留下容差空间;单层地板、孔洞或不匹配的几何层则不能保证存在合适的内部中点。
它要求可靠的 Watertight 几何,即没有破损开口、能够定义内外的封闭表面,还增加深度提取、存储和更新成本。实时系统需要比较实际帧时与收益:同为 的两个算法,常数、带宽和趟数仍可能相差很大。
阴影走样
自遮挡之外,有限分辨率还会使投影轮廓出现阶梯状边缘。一个阴影纹素可能在相机图像上覆盖很多像素,放大后就会直接看到离散边界。

图中墙上的人物阴影呈明显阶梯,方框标出局部边缘。这里主要是阴影轮廓的采样不足,与上一节表面深度比较造成的条纹需要分开处理。
提高阴影分辨率、改善投影范围和使用级联等策略可以改善采样分配,代价是更多显存或建图成本。之后介绍的 PCF 从比较结果的过滤入手处理边缘,不能把所有走样都仅靠一个大偏移消除。
乘积近似
为什么实时着色经常能先算光照,再乘一个阴影系数?可以从一个常见的积分近似理解:
右侧用 在积分域的平均值替代其逐点变化,然后保留 的积分。分母就是积分域的测度,是必要的归一化。例如 ,,、,平均值仍是 ,与直接提出常数完全一致。
它不是一般积分恒等式。用均匀域平均 表示,有
协方差 Covariance 衡量两者相对各自均值是否同时变大或变小;近似丢掉的正是这一相关性项。一个量在相关区域内接近常数时,丢掉它的变化通常更有根据。“光滑”在这里强调变化幅度小,只有连续或可导还不够。
若 只有很小的支持域 Support,即只有那片区域产生显著贡献,还应在这片有效区域考察 的变化。不能仅因 集中在一个点附近,就把整个大域的 平均值当作该点的值。
在光照问题中,令
则 可近似为平均可见率乘 ,这里 应取产生该光照贡献的相关方向域。理想点光源或方向光只有一个入射方向,可见性与该方向的着色贡献直接相乘;扩展面光源则是近似。
辐亮度均匀的较小面光源、漫反射响应,以及光源方向范围内变化不大的余弦,使近似较合理。强烈变化的环境光和窄高光则可能使遮挡与着色响应高度相关:遮住最亮方向和遮住暗方向,即便遮挡面积相同,也不应有相同照明结果。
硬影与半影
面光源具有有限面积。从接收点看,可能整块可见、整块被遮挡,也可能只看见一部分。完全被挡住的区域称为本影 Umbra,部分可见的过渡区称为半影 Penumbra。
太阳虽然距离很远,仍有有限角直径,因而通常能形成半影;理想方向光只有单一方向,是不同的模型。真实半影来自光源可见范围连续变化,不等于对一张完成的硬阴影图做任意模糊。
直接从面光源多处建立可见性再积分,成本较高。PCSS 使用一张从光源中心生成的阴影图,依据遮挡几何估计过滤宽度,得到软阴影近似。先理解它所使用的 PCF,再看宽度怎样决定。
PCF
Percentage-Closer Filtering,PCF 原本用于缓解阴影边缘走样。对同一个接收点,在阴影图的一个邻域内多次做深度比较,再对 结果求平均:
接收点和它的比较深度保持固定,变化的是阴影纹理中的查询位置。 是过滤权重,等权时为 。

蓝点表示当前查询所对应的位置,黑框强调邻域。若九次比较为
平均结果为 。它表示过滤后的可见率,不再只有完全明或完全暗两种结果。
比较与平均不能交换顺序。例如邻域深度为 ,接收深度为 ,逐个比较再平均得到 ;先平均深度得到 ,再比较却得到 。深度平均仍可用作统计量,但它不是 PCF 的可见率计算。
对最后渲染出的阴影区域做模糊也不同:那会混合不同相机像素、不同接收点的深度与材质;PCF 始终在当前接收点处改变阴影图查询。后处理可能掩盖某些边缘,却不能恢复已经丢失的几何采样。
接触软化
过滤区域小,边缘较锐;区域大,过渡较宽。固定的大 PCF 窗口虽然能产生模糊边缘,却不能自动表现真实光源与遮挡物的关系。

笔尖接近纸面,接触处阴影较锐;笔身离纸面更远,投影边缘更宽。这里“远近”首先指造成该处阴影的遮挡物与接收面之间的间距,不是简单按相机距离决定模糊程度。
Percentage-Closer Soft Shadows,PCSS 因此采用位置相关的 PCF 窗口。需要依次回答:哪些表面挡住当前点,它们平均有多远,据此该过滤多大。
半影估计
用平行光源平面、遮挡平面与接收平面的近似推导。设光源宽度为 ,从光源量到遮挡物和接收面的线性深度分别为 、。

绿色距离是 ,蓝色距离是 ,两者之差是遮挡物到接收面的间隔。光源两端经过同一遮挡边界,落到接收面上的位置不同;图中 就是这段半影宽度。
相似三角形给出
光源越宽,半影越宽;遮挡物靠近接收面时 趋近零,阴影趋硬;遮挡物接近光源时,比例增大。这里 是光源到遮挡物的深度,不是遮挡物到接收面的间距。
真实遮挡物可能有多层、弯曲或厚度,PCSS 用搜索区域内遮挡样本的平均深度 代替一个理想遮挡平面。这正是它的近似来源之一,并不能精确恢复任意面光源下所有遮挡关系。
由接收面上的几何宽度转为阴影纹理尺度,还要考虑投影。若阴影近平面距离为 、该平面宽度为 ,相应 UV 宽度约为
乘阴影图宽度才得到纹素宽度,使用半径时再除以 ;竖直方向应使用自己的视锥尺寸。几何宽度、UV 宽度和采样半径需要始终区分。
搜索范围
平均遮挡深度只对比接收点更靠近光源的样本求平均:
是在线性深度中定义的容差;若先用投影深度比较,应使用相应投影容差,之后再转换参与几何估计。没有遮挡样本时,这次搜索估计光源可见,不应对空集合做平均。
搜索区域与最后的过滤区域不是同一个问题。可以先用固定的 区域搜索,但它可能错过较大光源的相关遮挡物。更有依据的方法,是从接收点向光源范围连线,看这些线在阴影图所在平面上覆盖多大区域。

黄色面是光源,灰色面是阴影图的投影平面,红色区域是当前接收点需要搜索的范围。它包含可能挡住该点与光源连接的表面位置;最终 PCF 窗口则要在得到平均遮挡深度后才决定。
若光源位于 、阴影近平面为 、接收点为 ,从接收点看到的光源宽度投影到近平面时为
这是平行平面下的线性插值关系,再除以 转成 UV 范围。不同光源投影和实现会采用相应启发式,不能把固定纹素半径推广到所有尺度。
PCSS流程
核心步骤是搜索遮挡物、估计半影、按估计范围执行 PCF。每个接收点独立选择窗口:
1 | 把接收点投影到光源,得到 UV 和一致定义的深度 |
接触处窗口小,离开遮挡物后窗口变大,因此能产生接触阴影逐渐软化的效果。树干接近地面的阴影与树叶从较高处投向地面的阴影,就应有不同过渡宽度。
开销与限制
PCSS 的搜索和最终 PCF 都需要纹理访问。若两个阶段分别采用 、 个样本,每个接收点大致要做 次查询;若逐个遍历全部窗口,宽半影的成本会更大。
固定样本数、稀疏分布或 Poisson 风格的查询可以减轻成本,但也会带来噪声或漏检;样本分布与跨帧变化还影响时间稳定性。斜面、多个遮挡层和单张中心阴影图看不到的几何,是单平面与单视角近似的限制,不能靠增加 PCF 窗口完全消除。
多个光源分别需要建图和评估,相关成本通常随参与光源数量增加,但整体帧率并不必然严格变成 ,因为其他渲染阶段和光源覆盖范围也影响总时间。后续的统计阴影方法将用预过滤的数据减少窗口内的逐点访问,而不是假设这些查询没有成本。




