9.3 体积阴影与散射
介质里的阴影光线不再回答「挡不挡」,而是给出一条透射率曲线;几何遮挡由 BVH 求交单独判成「乘以 0」。本节把步进搬进带遮挡体的场景,量体积阴影的解析与步进对照、单次散射与二阶散射的差(贡献 18.14%、代价近 20 倍),以及各向异性相函数一次渲染重加权出的七档 g。
tag
介质里的阴影光线不再回答「挡不挡」,而是给出一条透射率曲线;几何遮挡由 BVH 求交单独判成「乘以 0」。本节把步进搬进带遮挡体的场景,量体积阴影的解析与步进对照、单次散射与二阶散射的差(贡献 18.14%、代价近 20 倍),以及各向异性相函数一次渲染重加权出的七档 g。
把 9.1 的连续积分换成沿光线取 N 个样点的求和,就得到 ray marching。本节把步长与误差的关系量成三张表:中点法二阶收敛直到定点地板、透射率链式累乘的偏差随 N 漂到 −5.17%、一阶近似在 σ_t·Δs ≥ 1 时透射率塌成 0 的条带阈值,以及双层介质里误差的震荡。
体积渲染方程把表面方程里「只在表面取值」的积分换成沿路径连续取值。本节用定点整数把三件事写成可断言的数:透射率 exp(-σ_t s) 的整数 LUT 与浮点 exp 差 2262 ppm、Henyey–Greenstein 相函数的归一化与各向异性、以及单次散射相对所有阶求和漏掉多少。
光子映射把能量当成粒子:从光源发射、在场景里弹、命中处记一笔,最后用 k 近邻密度估计在着色点还原辐照度。本节量出三个数:记录条件写成 b>=1 会让天花板直接项占比从 28% 掉到 18%;r² 掉到 3 ulp 时 1/r² 的凸性把估计值推高到 +63.3%;参考解自己的噪声底就有 14.11% 的 L1。
辐射度把腔体切成有限块,用形状因子矩阵把渲染方程变成线性方程组,一次解到底。本节量三件事:质心近似 cosθi·cosθj·Aj/(πr²) 的行和缺口在 n=2 时高达 17880 ulp(27.3%),不做行归一化解会整体偏低 15~23%;Jacobi 25 轮、Gauss-Seidel 14 轮收敛到同一残差。
直接光只算一次命中,剩下的能量全在多次弹射里。本节把渲染方程展开成按弹射次数排列的级数:0 次弹射差 41.4%,16 次才收敛到 0.49%。再对照自发光面积光源与环境常数两种源项——环境常数把均值调准到万分之零点二,却把漫反射像素的亮度标准差压到参考解的 45.1%。
余弦采样与光源采样各有一半场景吃亏,MIS 的做法是两条都采、再按权重函数拼起来。本节量出平衡启发式 `p_i/Σp_j` 与幂启发式 `p_i²/Σp_j²` 在三种光源尺寸下的方差:小光源与较优单路径打平(1.0447),中等与大光源分别降到 0.9053 与 0.6341;权重归一残差 ≤ 1,而幂启发式在 pdf < 1/256 时下溢成 0+0。
同一个直接光照积分有两条采样路径:余弦/BRDF 采样与光源采样(NEE),后者要把面积测度换成立体角测度,雅可比是 `p_ω = r²/(A·cosθ_l)`。本节把总辐射功率固定、只让光源半径从 0.15 变到 0.50,量出两条路径的方差交叉:小光源下光源/余弦方差比 0.065,大光源下 6.732。
蒙特卡洛估计量只有一个形状 `f/p`,方差全由采样用的概率密度 `p` 决定。本节先在半球上把 `p` 归一数清楚——漏掉 `1/π` 的 `cosθ` 会得 π 而不是 1;再用 `f(x)=2x` 量三条 pdf:匹配的 `2x` 让权重恒为 1、方差恰为 0,反匹配的 `2(1−x)` 把最大权重推到 255。最后把 `fdiv` 的截断误差与除零边界摆出来。
截断与轮盘赌都会让图像偏离参考解,但只有一个是错的:maxDepth 硬截断丢掉 (ab)^K 的尾部能量,是偏差;俄罗斯轮盘赌的权重补偿保持期望不变,只放大方差。本节用双板腔把两条曲线分开量,截断偏差从 0.44442655 降到 0.00010173 后被定点地板挡住,轮盘赌标准差从 0.459160 涨到 5.258522。
路径追踪把渲染方程改写成可估计的形式:方向按余弦加权采样,权重退化成 albedo 的连乘,一条路径就是一个样本。本节用确定性 SplitMix64 走完整条路径,量出 maxDepth 从 1 抬到 16 时平均亮度从 26032.93 爬到 33362.13,再用俄罗斯轮盘赌把每条路径的求交次数从 12.1170 砍到 2.7717。
渲染方程把一个像素的颜色写成第二类 Fredholm 积分方程:出射辐亮度等于自发光加上半球上入射辐亮度的积分。本节逐项拆解它,再用 Neumann 级数展成「每次弹射一项」的几何级数——双板腔真值 1.199936,截断 K 项的相对偏差恰为 (ab)^K;灯箱实测第一阶占 0.592314,第 8 阶之后只剩 0.00781676。
分层把立方体切成 4×4 格子,低差异序列让点自己避开彼此:同一个 d 球占比积分上,4096 个 Halton 点抵得上 7.4 万个随机点,log-log 斜率从 −0.4980 陡到 −0.7103。但这份红利随维度迅速蒸发,8 维时优势只剩 1.4 倍。
估计量的方差是 σ²/N,误差只能按 1/√N 降。本节把这条定律量成一个小数:7 档 N × 2048 条独立子流,log-log 最小二乘拟合出斜率 −0.4978,落在 −0.5 ± 0.03 内;再把 pdf 写错成 1.1,总误差被常数偏差压平到 −0.2638,而方差那条纹丝不动。
蒙特卡洛积分把「算一个积分」换成「估一个积分」:无偏估计量的均值收敛到真值,偏差则永远停在那里。本节讲清无偏性成立的条件,逐位核对 SplitMix64 的掩码语义——漏掉 `& RNG_MASK` 会让输出涨到 191 位、Q16.16 采样当场失效;再用 3 分箱卡方量出取模的量化偏置:值域 2^4 时 χ² = 15677,而 64 位拒绝采样只有 1.301。
光线树把「反射的光线还能再反射」显式组织成一棵按深度分层的树:depth 只是透传给着色器的参数,形状由着色器决定。实测每层光线 76800 / 44877 / 9232 / 6881 / 6269,代价随深度亚线性增长(每主光线 2.686 → 3.236 条),而在某一层截断会引入单边偏差而非无偏估计。
反射方向 r = d - 2(d·n)n 与 Snell 折射在 Q16.16 下逐位算清:4096 组随机单位向量的单位性偏差最大 55;eta = 1.5 时 3061 组全内反射;临界角公式与整数扫描在水 / 玻璃 / 金刚石三档互证到 1–2 LSB。菲涅尔 x^5 的平方链与逐次相乘实测不逐位相同(429/4096 差 1 LSB)。
硬阴影只有「被挡住」和「没被挡住」两种结果,判据是一条从命中点指向光源的阴影光线。本节把 `off` 是沿法线的偏移向量、不是起点这件事讲透:写错一行,77211 条阴影光线的遮挡数会从 1357 涨到 77210,画面只剩环境光。最后用一张方向光掩码数出像素层面的真阴影 839 个。
把 BVH 打开、关掉、再只去掉 best_t 收缩:三个模式跑同一条代码路径、同一批 76800 条主光线,用可逐条复现的整数计数(访问节点数、三角形测试数)与逐像素比对,回答加速结构到底省掉了什么、又省得够不够多;全部结论只建立在计数上,不做速度断言。
本节拆开 BVH 的构建:划分轴为什么按最长轴选、中位数划分平均花掉 SAH 最优代价的多少倍、叶子容量从 1 扫到 32 时每条光线的访问与测试各是多少,以及排序键的除法为什么必须走 floorDiv 才能在两个宿主上复现同一棵树、而不是只复现节点数。