14.2 CPU 与 GPU 的差异
把 CPU 定点与浏览器 WebGL2 之间的差异拆成三类可归因的来源:光栅化规则(顶左规则与子像素精度)、插值方式(屏幕空间仿射对透视校正)、浮点精度(Q6/Q16.16 对 float32)。每类配一个只动一个变量的实验,并用 MAE ≤ 1.0、PSNR ≥ 30 dB、覆盖归属差异 ≤ 20‰ 三条声明容差加一个必须被拒绝的负例来收口。
category
把 CPU 定点与浏览器 WebGL2 之间的差异拆成三类可归因的来源:光栅化规则(顶左规则与子像素精度)、插值方式(屏幕空间仿射对透视校正)、浮点精度(Q6/Q16.16 对 float32)。每类配一个只动一个变量的实验,并用 MAE ≤ 1.0、PSNR ≥ 30 dB、覆盖归属差异 ≤ 20‰ 三条声明容差加一个必须被拒绝的负例来收口。
光栅化与光追是两条完全独立的成像路径:一个在屏幕 Q6 空间用整数边函数判覆盖,一个在世界空间用 Möller–Trumbore 求交。同一份场景下两条路径的覆盖掩码各 43731 像素、逐像素对称差 4 个,且这 4 个全落在到最近正向边距离为 0 Q6 的轮廓边上;采样点挪半个像素,同一判据立刻变差到 160。
同一份 scene_spec.json 交给 Python 与 TypeScript,像素缓冲 sha256 同为 213b4130…、五个计数逐条相同。把相机基从行换成列,命中像素从 43731 掉到 21519,两侧却依旧逐字节相同——跨语言一致抓不到「两边一起错」。再把 BigInt 换成 Number,右移只有 32 位,命中像素静默变成 8484。
一张图是几万次累加拼出来的:整数加法三序零差异,破坏结合律的是 fmul 的截断(结合律反例 1005/4096)。重排三角形重建 BVH 后图像摘要一字不变而访问节点数变了;全局 RNG 流下换遍历顺序 3049/3072 像素改变,逐像素流则逐字节相同。
把 Möller–Trumbore 的误差写成一条闭式不等式,在 43731 条命中主光线上逐条核对:界恒成立,最紧处实测/界 = 0.746916。改一处舍入规则,41254 条命中的 t 就会分叉。真正决定自交的是垂直误差 Δt·sinθ,最大 241.47 LSB,而非沿光线的 Δt。
知道每个像素的误差量级之后,就可以让它自己决定什么时候停。本节实测三条停止准则(逐像素方差、相对误差阈值、预算上限)各跑两个最小样本数版本,量出各自停在哪、总样本数、最终误差,以及两种失效模式——四样本全零会让方差估计变成 0,于是最早停、错得最狠。
误差只在有参考解时才有意义,可参考解本身也是估计量。本节用四条互相独立的量法给它造不确定度:批均值的标准误、对半拆、换种子族的第二条参考解、以及只取全程无遮挡像素的确定性网格求积。实测 1024 样本/像素的参考解相对标准误 0.005729,对半拆比值 2.045、换族比值 2.219,都与理论吻合。
噪声不是「看起来脏」这种观感描述,而是一个可算的整数:同一像素在 N 个样本下是个随机变量。本节先把面光源半径取 0、N = 1 退化回卷三基线的逐字节锚点,再用 256 样本扫掠与 8 批参考解量出收敛速率——实测 log-log 斜率 -0.5027,理论值 -0.5。
有了求交点,法线靠差分、软阴影靠沿光取样,两者都不是免费午餐:差分步长 h 太大会跨过棱角、太小会被定点量化吃掉,实测中心差分在 h=64 时均值 0.0342 度;软阴影有一个系统性平面偏置 k*sin(theta),k 必须超过 1/sin(theta)≈1.732 才能让开阔地面回到满亮度。
sphere tracing 每一步走多远由场值自己决定,于是三件事必须量:迭代次数怎么分布、容差 eps 取多少、步长放大到几倍会出事。3072 条主光线的实测是解析命中 2048、marching 命中 1995、真漏交 0;无地面场景里场放大 1.1 倍只穿透不漏交,1.25 倍漏交 303.0303‰,1.5 倍直接 1000‰。
距离场要被 sphere tracing 当步长信任,必须同时满足下界性与 Lipschitz 界。本节用 40000 对随机点估计八个场的 Lipschitz 常数:球、盒、平面、交、差都落在 1000.0‰ 附近,高度场却到 1084.1‰;下界性实测球/盒/平面/并集的最大 f-d_true 恰为 0,高度场 648 个采样点里 426 个高估,最坏 3909 Q16.16。
程序化场景要能当资产用,靠的不是看起来像,而是可断言:同一 seed 必须给出同一场景。本节把 fBm 位移与噪声分档材质接进既有 scene_spec,基线帧 sha256 逐字节锚定 213b4130…,程序化场景的几何 digest 与帧 sha256 在同 seed 重跑下不变、换 seed 必变。
一个 octave 的噪声撑不起地形,fBm 按 gain^k 的振幅把它叠起来。本节用左移采样坐标实现 lacunarity=2、用 Q16.16 连乘 gain,全程整数:4 层高度场幅 132654,octaves=1 时逐位等于底座值噪声,与 8 层参照解的最大偏差 7551,结构函数斜率收敛到 0.533。
噪声在本章不是纹理而是几何的生成器,所以同一个 (ix, iz, seed) 必须给出同一个整数。本节用同一个 32 位混合哈希派生出值噪声与梯度噪声:128 点扫描线上均值 31041、标准差 14919,16384 个格点低 16 位的碰撞率 117.4‰,整数插值与 float64 参照的最大偏差只有 1 个 Q16.16。
底座的 trace 把 depth 一路传下去,却不把入射方向传给 shade。本节写一个与它逐字同构、只多传一个 dirv 的 trace_rec,给着色器加一层受深度门控的镜面反射:深度 1 的输出与卷三基线 sha256 逐字节相同(退化为直接光照),每加一层 MAE 与变化像素都单调下降。
相机三根基向量必须从 mat_look_at 的视图矩阵取行(第 0/1/2 行即 s / u / -f)。底座原先取列,把 up 的 z 与 back 的 y 各反一号,相机是歪的。判据是主光线命中掩码与卷一光栅化覆盖掩码的逐像素差:取行 43731 只差 4 个,取列 21519 差 22212。像素再经 NDC 与 4:3 宽高比合成主光线方向。
光线就是 o + t·d,求交就是把这个参数式代进一个隐式方程解出 t。本节用同一条探针光线对球、地面、三角形三种原语各求一次交:76800 条主光线归一化残差最大 5 Q16.16,命中 43731 个像素,三种 F(p) 残差分别是 712、0、-29。
《计算机图形学》卷三《光线追踪与全局光照》附录 A:全卷共用的定点常数表(EPSILON、TRI_STRIDE、BVH 叶子容量、SplitMix64 的四个常数)、光追中间量的实测位宽上界(为什么 TS 侧必须全程 BigInt)、十个渲染算法的整数形式速查、采样与收敛参数表、三卷基线 sha256 与误差汇总,以及各章产物的数据集索引与复现命令。所有数字来自本机实跑。
《计算机图形学》卷三《光线追踪与全局光照》目录:16 章 48 节,把光还原成一个可估计的积分——光线求交与 BVH、阴影反射折射、蒙特卡洛积分与方差、路径追踪与渲染方程、重要性采样与多重重要性采样、辐射度与光子映射、体积渲染与 ray marching、SDF、收敛度量与停止准则,最后做跨语言数值一致与三卷总收官。本页给出分工、六档可运行性口径、卷三新基线与全书 16 章 48 节完整清单。
在深度图上取 N x N 邻域逐个做整数遮蔽比较,可见度=(N^2-occ)/N^2,把硬边界摊成半影。8 顶点 / 4 三角形的极简场景里,N=1/3/5/7 的半影像素为 0/1103/2146/3059,与 N=15 参考的可见度 MAE 为 0.12926/0.11639/0.10724/0.09882,整帧 114.1 到 403.1 ms,收益按 1/N 递减。