阴影
需要预先学习:
1. ShadowMapping(阴影贴图)
着色与渲染管线 里提过,着色是局部的:每个点只根据光源与自身材质计算颜色,完全不知道其他物体的存在。所以 Blinn-Phong 管线本身画不出阴影——被遮挡的点和直接被照亮的点会得到相同的着色结果。ShadowMapping(阴影贴图)为光栅化管线补上这块能力,出发点是一个关键观察:
一个点不在阴影中,当且仅当光源能直接看到这个点。
"从某个视点看,谁能被看见"正是 深度缓冲 解决的问题。ShadowMapping 的思路,就是把深度测试搬到光源的位置再做一遍。
1.1 算法流程
ShadowMapping 分两遍(two-pass)渲染。
Pass 1:从光源渲染场景,只记深度。把光源当作一台摄像机——平行光配正交投影,点光源配透视投影(见 视图(摄像机)与投影)——渲染整个场景,但不存颜色,只把深度缓冲存下来。这张深度图就是阴影贴图(shadow map):它记录了从光源看过去,每个方向上最近的表面有多远。
**Pass 2:从摄像机正常渲染场景。**着色的同时,对每个片元做一次阴影判断:
- 把片元的位置变换到光源的观察空间,投影到光源的"屏幕"上,得到它对应的阴影贴图采样位置和深度;
- 采样阴影贴图,取出该方向上最近的表面深度;
- 比较二者:片元比记录中的最近表面更远,说明从光源看,这个片元前面还有别的表面挡着光——它在阴影中。
// 伪代码:Pass 2 中的阴影判断
float d_frag = to_light_view(fragment.pos).z; // 片元在光源视角下的深度
float d_map = shadow_map.sample(uv); // 贴图记录的最近深度
bool in_shadow = d_frag > d_map; // 更远 -> 被遮挡
判定为阴影的片元,着色时砍掉漫反射与高光,只保留环境光项——阴影处不是黑色,而是失去直接光照后变暗的固有色。
1.2 数值问题
ShadowMapping 的两个主要毛病都出在"逐像素比较两个深度"上。
自遮挡(self-shadowing):Pass 1 存下的深度是离散采样的结果,一块斜着的平面在阴影贴图里被量化成台阶状的深度。Pass 2 拿片元的精确深度去比较,一部分像素"刚好更远一点点",通过与否在像素间抖动,平面上出现黑白相间的条纹,俗称 shadow acne(阴影粉刺)。缓解办法是在比较时加一个偏移(bias),给量化误差留出容差。
bias 不是免费的:给多了,阴影整体向后退,物体与地面接触处的阴影断开,物体看起来像悬浮在阴影上方,称为 peter panning。bias 的取值需要在 acne 与漏光之间权衡。
走样:阴影贴图分辨率有限,当一张低分辨率贴图覆盖大范围场景时,一个贴图纹素对应屏幕上的一串像素,阴影边缘出现块状锯齿——本质又是采样不足(见 光栅化 的走样)。常见对策是提高贴图分辨率,或让近处场景使用单独的高分辨率贴图(级联阴影贴图,cascaded shadow map)。
注:以上问题各有后继的改进方案(如 variance shadow map、moment shadow map),但"光源视角记深度 + 摄像机视角比深度"的两遍框架始终没变。
2. 软阴影与硬阴影
ShadowMapping 的判断结果只有"是 / 否"两种,画出的阴影边界锐利、非暗即亮。这一节看看真实阴影的形态,以及它为什么不是这样的。
2.1 硬阴影
边界一刀切的阴影称为硬阴影(hard shadow)。它隐含了一个假设:光源是一个点,或一个无穷远的方向——只有点光源的遮挡才会产生绝对分明的边界。
现实里不存在无限小的光源。
2.2 本影与半影
光源有面积时,遮挡是一个程度问题。从光源两端点经过遮挡物边缘作连线,投影面上会出现两类区域:
████████ 面光源
\ /
\ / <- 光源两端发出的边界光线
▄▄▄▄
████ 遮挡物
▀▀▀▀
/ \
/ \
━━░░░▓▓▓▓░░░━━ 投影面:▓ 本影,░ 半影
- 本影(umbra):从这一点完全看不到光源,阴影最暗;
- 半影(penumbra):从这一点能看到光源的一部分,明暗介于本影与无遮挡之间。
2.3 软阴影
软阴影(soft shadow)就是半影在视觉上的表现:阴影边缘有一段从全暗到全亮的渐变,而不是一刀切的边界。它传递了两个物理事实:
- 光源有面积;
- 半影的宽度与遮挡物到接收面的距离相关——遮挡物贴着接收面时阴影边缘锐利,悬得越高边缘越柔和。反过来说,从一张照片里阴影的软硬程度,能推断出光源的大小。
光栅化下实现软阴影的基本思路,是把"在不在阴影中"的二值判断改写成"光源被遮挡的比例":
- PCF(percentage closer filtering):比较深度时不止查一次,而是在阴影贴图上取一个邻域多次采样,把多个"是 / 否"的结果平均成 0 到 1 之间的系数,阴影边缘得到过渡;
- PCSS(percentage closer soft shadows):在 PCF 基础上,让滤波范围随遮挡物与接收面的距离动态变化——近处滤波窄(阴影硬),远处滤波宽(阴影软),与半影的几何规律一致。
注:PCF是用于消除硬阴影锯齿的方法,PCSS 才是真正模拟软阴影的算法。PCF 只是参与了 PCSS 的滤波阶段。


