惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
Stack Overflow Blog
Stack Overflow Blog
人人都是产品经理
人人都是产品经理
The GitHub Blog
The GitHub Blog
Engineering at Meta
Engineering at Meta
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Y
Y Combinator Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
B
Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
Google DeepMind News
Google DeepMind News
D
DataBreaches.Net
博客园 - 司徒正美
B
Blog RSS Feed
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog

KSkun's Blog

Unity 构建与客户端安全 | KSkun's Blog 2022:世界线收束中 | KSkun's Blog 2021~2022 年丢失博文的备份 | KSkun's Blog 博客挂的这几个月 | KSkun's Blog 2021 路在脚下(备份补档) | KSkun's Blog VoIP 中的话音失真问题分析 | KSkun's Blog 2020, FUCKOFF | KSkun's Blog 随机数生成算法与其图形应用 | KSkun's Blog Codeforces Round #670 (Div. 2) 题解 | KSkun's Blog
GAMES 202 笔记:Shadow Mapping | KSkun's Blog
KSkun · 2022-10-10 · via KSkun's Blog

施工中……现在没有写的部分还有:

  • Convolution Soft Shadow Mapping
  • Moment Shadow Mapping
  • Mipmap 和 SAT 的原理

虽然没打算写,但有可能会加的部分有:

  • 这几种阴影、软阴影的 GLSL 实现(看另外一个 OpenGL 渲染器的坑)

注:头图来自 GAMES 202 作业 1 任务指导书,文中部分插图来自互联网资料,引用源均在参考资料中列出。

阴影贴图(Shadow Mapping)

阴影贴图是一种 2-pass 的实时阴影渲染技术。它在阴影 pass 中以光源视角绘制阴影贴图,再在着色 pass 中利用阴影贴图的信息计算直接光照,这个过程类似将阴影生成为贴图,并应用在阴影投射到的物体(receiver)上,故如此称呼。

原始论文:Casting curved shadows on curved surfaces,发布于 SIGGRAPH 1978,作者 Lance Williams (NYIT)。

流程

判断一点是否处于阴影中,即是判断该点和光源的连线上是否存在遮挡物。这可以简单地描述为:从光源的视角能否看到遮挡物,或光源沿此方向看到物体的深度是否小于该点。要得到光源看向场景的深度信息,可以直接使用绘制场景相同的流程,而将绘制结果写入一张深度图(depth map)中即可。在计算直接光时,将该点坐标变换到光源视角中,查询深度图做比较来判断是否被遮挡。因此可以将这一过程分为两个 pass 来实现:

  1. 深度 pass。设置相机参数在光源处看向场景,render target 为一张深度图,并选择恰当的相机、贴图参数使场景完全包含在内。提交场景后,得到光源视角看向场景的深度信息,还需保存下此 pass 的变换参数。
  2. 渲染 pass。在计算光照时需要着色点的世界坐标,将其变换到光源视角中,得到深度图纹理坐标(即光源视角下 NDC 坐标系的 x、y 坐标标准化值)。使用这个坐标查询深度图,如果深度与着色点到光源距离一致说明未被遮挡,如查询深度更小说明被遮挡。

以上为基本流程,但更好的效果需要加入一些修改,详见讨论部分。

讨论

光源变换矩阵的参数

如果使用一张 2D 贴图来保存阴影信息,需要选择合适的参数。点光源应选择透视投影,而平行光源应选择正交投影,这关系到投射出阴影的形状是否正确。视锥体应该包含全部场景,远、近平面、视锥边界参数应该取得足够大,否则可能导致深度图查询越界。如果光源没有办法从一个方向看到整个场景,应该考虑生成多张不同角度的深度图,或使用 Cubemap、球形贴图等保存。

此处再整理一下流程中涉及的不同坐标系。在查询深度图时,需要将着色点的世界坐标变换到光源视角的 NDC 坐标下,这通过乘光源的视角变换和投影变换矩阵(V、P 矩阵)实现。NDC 坐标通常在 [-1, 1] 范围内(取决于 API 定义),因此还需要进一步标准化到 [0, 1] 范围,得到深度图上的纹理 UV 坐标。

自遮挡:数值误差

深度图将标准化深度值保存在贴图像素上,每个像素的颜色值是固定位数的,因此能表示的精度也是有限的,这会为数值带来量化误差。此外,深度图代表遮挡物投影至平面上的结果,但遮挡物不一定与投影平面平行,深度图上的像素实际上代表了与投影平面平行的小面元,这也会带来离散化的误差。

Shadow Acne 现象
离散化误差

这些误差使得查询深度图的结果可能比真实遮挡物深度偏大或偏小,偏大不会造成严重后果,但偏小可能让遮挡物本身以为自己被遮挡,在没有阴影的地方形成奇怪的暗色斑点、纹路(被称为 Shadow Acne)。可以在判断被遮挡时,在着色点深度加上一个正偏差补偿数值误差,使数值误差不影响遮挡判断结果。

Peter Panning 现象

这种误差补偿是有局限性的。当遮挡平面与光源视线成较小角度时,离散化误差将变大,很难补偿这种情况的误差。而阴影遮挡物和被遮挡物离的很近时,被遮挡物的深度又被偏差补偿到小于遮挡物,使得一些阴影没有和遮挡物连起来,看起来好像遮挡物浮空了一样(被称为 Peter Panning)。可以在生成深度图时使用背面剔除,这样可以解决有厚度遮挡物的问题。

阴影的锯齿走样

由于深度图本身的尺寸有限,阴影投射后放大出现了锯齿状走样。这种走样和渲染中出现的锯齿现象原理类似,可以使用一些采样方法解决。一个方式是 PCF(百分比渐进滤波,Percentage Closer Filter)。着色点映射到深度图上后,不只查询一个像素,而是采样该像素附近一圈像素。采样的范围越大,阴影边缘的过渡部分越宽,锯齿现象越不明显,但阴影边界也会变模糊。

百分比渐进软阴影(PCSS,Percentage-Closer Soft Shadows)

Shadow Mapping 能为实时阴影计算提供遮挡信息,从而方便地实现点光源、平行光源的阴影。然而,光源常常并非二者之一,而是有一定面积的面光源。在面光源下,阴影分为本影(umbra)和半影(penumbra),本影是光源面积完全被遮挡的位置,亮度最暗,而半影是本影周围光源面积部分被遮挡的位置,形成了最暗到最亮的过渡带。半影形成的过渡带使阴影的边缘模糊,形成软阴影(soft shadow)的效果。

PCSS 受到 PCF 滤波效果的启发,通过指定阴影不同位置 PCF 滤波范围大小,来实现类似真实软阴影的效果。这种方法完全基于 Shadow Mapping 的结果,没有额外的前、后处理,也不需要更多信息。

原始论文:Percentage-Closer Soft Shadows,发布于 SIGGRAPH 2005,作者 Randima Fernando (Nvidia)。

流程

PCSS 通过控制 PCF 滤波范围大小来实现阴影不同程度的软化,因此需要确定着色点处需要选择的 PCF 滤波范围大小,这个大小与光源、遮挡物和被阴影投射物之间的集合关系决定。

上图所示的场景中,假设光源、遮挡物和投射物平面是平行关系。黄色的线段表示有面积的光源,其宽度为 $w_\text{Light}$,光源被遮挡物(绿色线段)左侧遮挡,其阴影投射在下方的投射物(蓝色线段上),遮挡物到光源距离 $d_\text{Blocker}$,投射物到光源距离 $d_\text{Receiver}$。从光源两端点经遮挡点向投射物连线(黄色虚线),投射物两连线的右方表示两点分别无法照亮的位置,即本影,宽度为 $B$;而两连线之间的部分表示光源被部分遮挡,即半影,宽度为 $w_\text{Penumbra}$。这样就建立了阴影和场景的几何关系。注意到存在一系列相似三角形关系,存在比例
$$ \dfrac{d_\text{Blocker}}{d_\text{Receiver}-d_\text{Blocker}} = \dfrac{w_\text{Light}}{w_\text{Penumbra}} $$
故半影宽度可以由
$$ w_\text{Penumbra} = \dfrac{d_\text{Receiver}-d_\text{Blocker}}{d_\text{Blocker}} \cdot w_\text{Light} $$
求得。另外,此处的两个 $d$ 值可以不是图中的垂线段长度,满足相似三角形的比例关系皆可。

经过上面的讨论,半影宽度与光源大小、遮挡物深度和着色点深度有关,后两个量可以分别从深度图与世界坐标得到,光源大小通常是预定义好的。PCSS 的工作过程可以描述为下面三步:

  1. 遮挡物搜索。由于光源有面积,能够遮挡着色点的遮挡物可能存在于一个区域中(即上图着色点和光源连线成的锥形区域),这片区域投影在深度图上也会占据一片区域。因此,要得到遮挡物的深度,需要查询所有可能遮挡到着色点的区域,求出其中遮挡物的平均深度值,这一步被称为遮挡物搜索。查询深度图的区域大小与着色点深度成正比,可定义一个比例系数作为参数。通常不会查询区域内的每个像素,而是采用采样方法估计搜索结果。
  2. 半影大小估计。上面的理论推导中得到了关系 $w_\text{Penumbra} = \dfrac{d_\text{Receiver}-d_\text{Blocker}}{d_\text{Blocker}} \cdot w_\text{Light}$。上一步中得到了 $d_\text{Blocker}$,而 $d_\text{Receiver}$ 可通过世界坐标中着色点到光源中心的距离估计,直接通过上面的关系计算半影大小即可。
  3. PCF 滤波。将半影大小作为 PCF 滤波范围大小,对深度图进行采样和滤波即可。PCF 滤波的过程可参考上文。

讨论

PCSS 是一种基于 PCF 的很直接的思路,实现的难度也不高。然而由于遮挡物搜索和 PCF 两步中包含两次采样估计过程,PCSS 的效果通常包含许多噪声,需要提高采样数或应用低通滤波等方式改善渲染质量。此外,多次采样会带来大量的贴图查询,带来时间和带宽上的额外开销,效率上依然存在问题。随着时域、空域滤波的技术发展,现在有 TAA 等更好的方法改善 PCSS 的噪声和效率,因此 PCSS 仍然是一种广泛使用的方法。

方差软阴影(VSSM,Variance Soft Shadow Mapping)

PCSS 中需要范围查询深度图中的信息来完成遮挡物搜索和 PCF,而范围查询需要通过采样来完成,这会为渲染结果带来采样造成的随机噪声。为了避免这种噪声,有许多方法通过非采样的方法来估计遮挡物的深度。VSSM 是一种利用统计规律来估计遮挡物深度的方法,它基于 Chevbyshev 不等式和方差阴影贴图(VSM,Variance Shadow Mapping)方法,通过使用预计算的信息来避免采样。

原始论文:Variance Soft Shadow Mapping,发布于 PG 2010,作者杨宝光(Autodesk、浙江大学)。

流程

在 PCSS 中,遮挡物的平均深度通过对深度图的采样获得。从这个思路出发,设采样数为 $N$,其中有 $N_1$ 个样本为非遮挡物(深度不小于着色点)、$N_2$ 个样本为遮挡物(深度小于着色点)。记遮挡物的平均深度为 $z_\text{occ}$,非遮挡物的平均深度为 $z_\text{unocc}$,所有样本的平均深度为 $z_\text{Avg}$,则根据平均值的定义可得下面的关系
$$ \dfrac{N_1}{N} z_\text{unocc} + \dfrac{N_2}{N} z_\text{occ} = z_\text{Avg} $$
采样的频率会收敛到概率,故可以用概率来估计样本的频率,此外所有样本的平均深度也可近似为真实的平均深度,则记着色点深度为 $t$,采样的随机变量取值为 $x$,上式可化为
$$ P(x \ge t) z_\text{unocc} + [1 – P(x \ge t)] z_\text{occ} = z_\text{Avg} $$
那么遮挡物的平均深度便可通过下式求出
$$ z_\text{occ} = \dfrac{z_\text{Avg} – P(x \ge t) z_\text{unocc}}{1 – P(x \ge t)} $$
有了这个关系,还需要知道非遮挡物的平均深度 $z_\text{unocc}$,以及实际意义为深度图查询范围中不小于采样点深度的像素占比 $P(x \ge t)$。

对于非遮挡物的深度,一般来说着色点附近的非遮挡物是着色点所在的平面,因此可以近似为着色点的深度 $t$。而遮挡物的占比则可以借助 VSM 的思路解决,对深度图查询范围进行采样,其对应的随机变量应是范围内像素构成的离散型随机变量,对随机变量成立单边 Chevbyshev 不等式如下(证明见附录)
$$ P(x \ge t) \le \frac{\sigma^2}{\sigma^2+(t-\mu)^2}, \forall t > \mu $$
一个会让上式的不等号取等的情况是查询范围内的深度值都一样,而实际情况中深度值常常相差不大,使用上界值估计实际情况不会有很大误差,此处取该上界的值作为 $P(x \ge t)$ 的估计。要计算这个上界,需要得到查询范围内深度的平均值 $\mu$ 和方差 $\sigma^2$,平均值可以通过 Mipmap 或 SAT 等方式得到,而方差可以通过关系 $\sigma^2 = E(x^2) – \mu^2$ 得到,因此只需要另外计算一张深度平方的深度图和它的 Mipmap/SAT 即可。

上面的理论推导得到了重要的结论,即仅需要保存深度图、深度平方图和它们的 Mipmap/SAT,就可以通过单边 Chevbyshev 不等式的上界估计平均遮挡物深度,避免了 PCSS 中遮挡物搜索的采样操作。而 PCSS 中 PCF 滤波的采样操作,则可以通过对估计出的滤波范围大小再次进行非遮挡物占比(即 $P(x \ge t)$)的估计,将其作为可见性的值,同样也可以实现无采样的滤波效果。故 VSSM 的整体流程为

  1. 遮挡物平均深度估计。使用与 PCSS 同样的方法得出遮挡物搜索范围的大小,在这个范围中查询 Mipmap/SAT 得到均值 $\mu$ 和平方均值 $E(x^2)$,从而计算出方差 $\sigma^2 = E(x^2) – \mu^2$,进而估计出范围内非遮挡物占比 $P(x \ge t) = \dfrac{\sigma^2}{\sigma^2+(t-\mu)^2}$,再根据 $z_\text{occ} = \dfrac{\mu – P(x \ge t) \cdot t}{1 – P(x \ge t)}$ 求出遮挡物平均深度的估计。
  2. 半影大小估计。这一步与 PCSS 一致。
  3. 可见性估计。将上一步求出的半影大小作为查询范围大小,同样地计算 $P(x \ge t)$,该值就是可见性值的估计。

讨论

VSSM 利用了单边 Chevbyshev 不等式作为平均遮挡物深度 $z_\text{occ}$ 的估计,然而不等式需要满足单边条件 $t > \mu = z_\text{Avg}$,通常在着色点附近是平面时这个条件能够被满足。当着色点附近的点和着色点不在一个平面上,尤其是远离光源方向时,实际的 $z_\text{Avg}$ 可能比着色点深度 $t$ 更大,此时使用原来的估计会使 $P(x \ge t)$ 的值小,从而使 $z_\text{occ}$ 的估计可能大于 $t$,一些本应在阴影中的像素被判断成不在阴影中,如下图所示。

引起问题的原因是着色点附近不是平面,如果把查询范围划分成更小的格子,小格子中的几何会更接近平面,更可能满足不等式的条件。对于划分后仍然不满足条件的小格子,可以简单认为不被遮挡,或使用常规采样方法进行 PCF。

划分后,每个满足不等式条件格子 $w_{cj}$ 的 $E(x)$ 和 $E(x^2)$ 可以通过 SAT/Mipmap 直接查出,记格子的大小为 $T_{cj}$,则可以通过每个格子的信息合并得整体的 $\mu, \sigma^2$,如下所示

$$ \begin{aligned} \mu &= \dfrac{\sum_j E(x)_{cj} T_{cj}}{\sum_j T_{cj}}, \\ \sigma^2 &= \dfrac{\sum_j E(x^2)_{cj} T_{cj}}{\sum_j T_{cj}} – \mu^2 \end{aligned} $$

满足条件格子的平均遮挡物深度 $d_1$ 可以通过 $\mu, \sigma^2$ 和不等式估计得到。而不满足条件的格子则使用上文提到的采样方法,得到其平均遮挡物深度 $d_2$。根据两类格子占比对 $d_1, d_2$ 加权得到最终的平均遮挡物深度 $d$,这样一来就解决了非平面的问题。

此外,方差 $\sigma^2$ 具有的统计意义可以反映随机变量分布的集中特性,如果方差很小且 $z_\text{Avg}$ 大于着色点深度 $t$,说明着色点附近基本都是更深的点,此时可以直接认为着色点未被遮挡以减少计算。

参考资料

附录

单边 Chevbyshev 不等式的证明

参考资料:https://zhuanlan.zhihu.com/p/111329527

Markov 不等式

设 $X$ 是取非负值的随机变量,则对于任何常数 $a>0$,有
$$ P(X \ge a) \le \frac{E(X)}{a} $$

证明 对于 $a>0$ 令随机变量 $I = \begin{cases} 1, & X \le a \\ 0, & X < a \end{cases}$,由于 $X>0$ 有 $I \le \dfrac{X}{a}$,两边求期望得 $E(I) = P(X \ge a) \le \dfrac{E(X)}{a}$。

单边 Chevbyshev 不等式

设 $X$ 具有 0 均值和有限方差 $\sigma^2$,则对任意 $a>0$ 有
$$ P(X \ge a) \le \frac{\sigma^2}{\sigma^2+a^2} $$

证明 引入常数 $b>0$,则有 $X \ge a \Rightarrow X+b \ge a+b \Rightarrow (X+b)^2 \ge (a+b)^2$,因此有
$$ P(X \ge a) = P(X+b \ge a+b) \le P[(X+b)^2 \ge (a+b)^2] $$
由 Markov 不等式可进一步推得
$$ P(X \ge a) \ge P[(X+b)^2 \ge (a+b)^2] \le \frac{E[(X+b)^2]}{(a+b)^2} = \frac{\sigma^2+b^2}{(a+b)^2} $$
既然上式对任意常数 $b>0$ 都成立,取右式关于 $b$ 的极小值时 $b = \dfrac{\sigma^2}{a}$,得到
$$ P(X \ge a) \le \frac{\sigma^2}{\sigma^2+a^2} $$

推论 设 $X$ 均值为 $\mu$,则上述结论式可变为对任意 $a>\mu$ 有
$$ P(X \ge a) \le \frac{\sigma^2}{\sigma^2+(a-\mu)^2} $$

取等条件 其中一个充分条件为 $P(X=0)=1$。