16.3 交付卷三
卷二的收尾交付:接口契约写清卷一封了装配/深度/覆盖、卷二封了着色链(片元颜色的来源),卷三只替换「直接光照求值」为渲染方程的积分估计;另给出三条轴的参数扰动扫描——强度偏移 1 个 Q16.16 单位即产生首次可见差异(128 像素),超采样 N=4 才收敛,10 条软告警不硬失败、exit 0。
category
卷二的收尾交付:接口契约写清卷一封了装配/深度/覆盖、卷二封了着色链(片元颜色的来源),卷三只替换「直接光照求值」为渲染方程的积分估计;另给出三条轴的参数扰动扫描——强度偏移 1 个 Q16.16 单位即产生首次可见差异(128 像素),超采样 N=4 才收敛,10 条软告警不硬失败、exit 0。
同一份 scene_spec.json 在 Python 定点、TypeScript 定点、浏览器 WebGL2 三处消费:S 与 P 成片 sha256 逐字节相同(0d997343…,43731 覆盖像素),S 与 W 按声明容差比对为 MAE 0.5067、PSNR 47.03 dB、覆盖归属差 0.0781‰;负例(片元颜色 ×0.5)MAE 21.3528 被同一判据拒绝。
把卷二前面十五节的环节首尾相接成一条从规格到成像的链:S 层在 320x240 场景上逐级核对十二个缓冲契约,w 不变量 487/487 成立、重心权重和违反 0 次、覆盖 43731 像素,成片 sha256 与卷二基线逐字节一致;P 层用 BigInt 重算同一链路复现同一摘要,负例(顶点顺序交换)必须让摘要分叉。
把同一份场景交给 WebGL2 渲染,再与 S 层定点基线逐像素比对:MAE 0.5073、PSNR 46.95 dB、覆盖归属差异 0.08‰,与 12.1 的 CPU 浮点镜像 0.4963 落在同一噪声地板上。不一致像素按「共享边归属 0.0% / 插值精度 11.3% / 舍入 88.6%」三类归因,并如实交代本机后端是 ANGLE/SwiftShader 软件实现,不是真实 GPU。
sha256 是「全等判据」,只在「全等是合理期望」时成立——浮点实现的 sha256 与基线不同,而它的 MAE 只有 0.4963。本节给出 MAE / RMSE / PSNR / 最大通道差四个标量的定义与各自盲区:整幅 +1 与 4 像素 +64 的 MAE 完全相同,只有 RMSE 与最大通道差分得开;覆盖归属差异与颜色误差是两条独立判据,必须分两栏报告。
逐位相等在两个宿主之间不可能,容差必须可声明、可复算。本节把容差写成三条同时成立的判据——覆盖归属差异 ≤ 20‰、MAE ≤ 1.0、PSNR ≥ 30 dB,逐条给出阈值出处,并用同一份整数基线量出噪声地板与三条穿越点:太松会漏掉 2% 的全局偏置,太紧会把噪声本身判成错。
调色的整数形态是查表:对比度绕 128 旋转、色温用逐通道偏置,合成三张 256 项的一维 LUT;饱和度写成 luma + (s·(in-luma) >> 16)。本节量化 8 位 LUT 的往返代价与三维 LUT 的节点数误差,并给出 W 层浮点着色器与 S 层整数结果的声明容差比对(含被拒绝的负例)。
泛光不是「把亮的地方涂糊」,而是一条四步整数链:Rec.601 亮度做整数阈值提取亮部、每级减半的 2×2 盒式降采样、整数双线性上采样、以及权重和恰好为 ONE 的合并。本节扫描级数 2/4/6、阈值 64/96/128、强度 0.25~2.0,并给出金标准泛光帧的 sha256。
后处理的第一步是用核卷积。本节把卷积写成全整数:Q16.16 核权重之和必须恰好等于 ONE,否则整帧会整体偏亮或偏暗;边界有 clamp/wrap/mirror 三种;半径 r 的二维核拆成两次一维可分离卷积,半径 8 时实测快 8.86 倍。Python 与 TypeScript 在四个半径下的模糊缓冲 sha256 逐字节相同。
介质里的阴影光线不再回答「挡不挡」,而是给出一条透射率曲线;几何遮挡由 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。