4.2 分辨率、偏差与走样
把阴影偏差解析地推出来:深度 1 LSB 对应 80 个 Q16.16 世界单位,纹素世界尺寸 20480、tan(theta)=1.4142,故解析上界 28963,而实测每像素所需偏差中位数只有 1、p95 184。再扫出偏差权衡曲线:bias 从 0 加到 4096,自遮挡从 22360 降到 0,阴影脱离从 17 升到 459;分辨率 64² 到 512² 自遮挡降 38 倍。
category
把阴影偏差解析地推出来:深度 1 LSB 对应 80 个 Q16.16 世界单位,纹素世界尺寸 20480、tan(theta)=1.4142,故解析上界 28963,而实测每像素所需偏差中位数只有 1、p95 184。再扫出偏差权衡曲线:bias 从 0 加到 4096,自遮挡从 22360 降到 0,阴影脱离从 17 升到 459;分辨率 64² 到 512² 自遮挡降 38 倍。
把 487 顶点 / 896 三角形的烛影山谷用光源正交矩阵投到 256x256 深度图,再在主相机逐像素插值出光源空间坐标 (lu,lv,ld),整数比对 ld-map>bias 判定遮蔽:深度图覆盖 11409 纹素、光源趟剔除 192 三角形、主视图 43731 像素、硬阴影 1360 像素(31.1‰)。P 层与 S 层像素缓冲逐字节相同,W 层按声明容差收敛。
噪声的终点是可用的纹理。本节把 fBm 与距离场组合成云、木纹、石纹三张 64×64 贴图,全部走整数:距离用 fsqrt、环带与脉线用整数取模、颜色是 8 位通道的整数混合。判据是像素缓冲的 sha256——Python 与 TypeScript 合并缓冲同为 4ef471f3…,共 36864 字节逐字节相同。
单层噪声太规整,fBm 把它叠成分形。本节用整数实现 octaves / lacunarity / gain 三个旋钮,在 0.125 世界单位步长上扫描 1/2/4/8 层:归一化粗糙度 0.0405 → 0.0509 → 0.0714 → 0.0918 单调上升,方差反而从 13653 降到 8481;再做整数域扭曲,看细节被拧成条带。
站内那篇讲的是大地形 LOD、分块与流式加载的工程实现,本节把噪声函数本身拆成可逐值断言的整数。值噪声与 Perlin 共用同一个整数哈希,只在格点上放标量值还是梯度向量:128×128 实测两者均值 32138 与 98,相邻差分均值 4703.5 与 7464.1,功率谱峰值分别落在第 0 与第 2 个频带。
像素足迹是 UV 空间的平行四边形,过滤核取它的内切椭圆,半轴比就是各向异性比。本节从 UV 梯度算出椭圆、构造 ripmap,再量 N=1/2/4/8 的采样曲线:MAE 25.99 降到 19.76 且收益递减,ripmap 单次查表 11.00,代价是显存 ×3.88。
从最近邻到三线性:双线性用四个 Q16.16 权重累加后一次 >> 32 降精度;mip 链是逐级 (a+b+c+d) >> 2,奇数尺寸要决定最后一行列是保留还是截断;LOD 用整数位长求 floor(log2)。对 4×4 超采样参考解,最近邻 MAE 54.41、mipmap 14.15、三线性 10.56。
UV 是一对 Q16.16 定点数,落格子就是一次算术右移。本节把 wrap / clamp / mirror / border 四种寻址全做成整数运算——位与、钳位、向下取整的模、越界短路——再用一张自描述纹理把采样结果反查回 texel:64 列里只有 12 列四种模式给出同一结果。
把 sRGB 接进卷二着色链路并量出代价:8 位量化误差最大 218 Q16.16、均值 40.10,往返不变量残差 0 灰度级;线性域取中点与 sRGB 域取中点最多差 61 灰度级;端到端「线性着色 + 编码」与「直接在 sRGB 域着色」MAE 15.57、PSNR 24.28 dB。
把 Reinhard、扩展 Reinhard(白点)与 ACES filmic 近似三条色调映射曲线写成全整数,喂同一份《烛影山谷》HDR 辐射度(曝光 6.0 后 max 4.33),实测输出曲线与 8 位直方图:Reinhard 均值 165.89 不裁剪、扩展 Reinhard 裁掉 156 像素、ACES 均值 213.43。
把 sRGB 传递函数固化成整数查找表:解码表 256 项一次生成后冻结,编码用整数二分在边界表上反查,Python 与 TypeScript 读同一份整数故逐位一致。实测 8 位往返残差 0 灰度级、线性往返最大误差 292 Q16.16,并给出线性段与幂段的分段边界。
6.1 的 BRDF 和 6.2 的参数映射都默认反射能量不超过入射能量,但 BRDF 本身给不出这个保证。本节把 ∫f·cosθ·dω≤1 变成一个可跑的整数实验:GGX 各粗糙度的残差表、alpha=1 处的解析锚点 1-ln2、单次弹射丢掉 69.3% 能量、Kulla-Conty 补偿把它补回 1,以及 kD=1-F 为什么守不住掠射角。
金属度-粗糙度工作流只用两个标量描述一个表面:基础色在绝缘体时是漫反射 albedo、在金属时是镜面 F0,粗糙度平方后映射成 GGX 的 alpha。本节扫过粗糙度 0/0.25/0.5/0.75/1,量出 NDF 峰值从 809 掉到 0.32、半高角从 0.76 度张到 90 度,并说明为什么高光宽度要用 NDF 半高角而不是 BRDF 的半高全宽。
微表面理论把镜面反射拆成 D、G、F 三个因子:GGX 分布 D 决定高光形状,Smith 几何项 G 补偿微面互相遮挡,Schlick 菲涅尔 F 决定反射比例。本节在 Q16.16 里把它们写成纯整数,用投影圆盘方向集验证 ∫D(n·h)dω≈1,并量出 alpha=0.25 时镜面波瓣的半高全宽是 40.74 度。
环境贴图把整个天空的入射光存下来,球谐用 9 个系数(L0/L1/L2)把它压成可解析重建的方向函数。本节用整数 Fibonacci 格点做投影,得到 9 个 Q16.16 系数,重建辐照度与直接积分只差 2‰;但太阳圆盘这类高频特征装不进 9 个系数,L2 带被它拉偏,反而在低频背景上引入振铃。
SSAO 从深度缓冲重建视空间位置与法线,再在半球上撒一组固定方向做遮挡估计。方向集固定会让误差呈低频带纹,把它按一张确定性的 4×4 旋转表逐像素转开,误差被打散成高频噪声,再用 3×3 盒式模糊压回去。本节量出 4/8/16/32/64 采样的收敛曲线、旋转前后的粗糙度,以及模糊对内部像素误差的削减,并在 TypeScript 侧逐字节复现。
环境光遮蔽衡量一个点周围半球有多大比例能看见天空,是环境光里最便宜的一阶近似。本节先构造一个能解析求解的最小场景——平面上方悬一个球,正下方那点的遮蔽恰为 1 减去 r/h 的平方——再用一组固定的 64 条整数方向做逐像素数值估计,量出它与解析值的误差、整幅 AO 图的 MAE,最后把 AO 作为乘性项接进着色,并在 TypeScript 侧复现同一张 AO 图。
把 32x32 的 8 位法线贴图还原成 128x128 世界法线:解压 n=(v<<17)/255-ONE 后未归一化模长落在 [0.9905,1.0024],归一化后偏差最大 2,与连续法线夹角最大 0.7753°。W 层用 WebGL2 片元着色器做同一件事,与 S 层按声明容差比对 MAE 0.5068 / PSNR 51.08 dB / 覆盖差异 0‰,负例被同一判据拒绝。
切线空间从三角形的三个顶点位置与三个 UV 解出:标准 UV 下 denom=65536、T=(2,0,0)、B=(0,0,2),手性 w=-1;把 u 反向令 denom=-65536 后 T 翻成 (-1,0,0)、w 变 +1。相邻面平均出的切线 T_avg=(1,0.5,0) 模长 1.1180、n·T_avg=0.5,Gram-Schmidt 后回到正交单位基。
法线不能直接用模型矩阵 M 变换:非均匀缩放 diag(2,1,1) 下 (M·t)·(M·n) 实测 +3.0000 而非 0,改用逆转置 (M⁻¹)ᵀ 后恒为 +0.0000;纯旋转时两者相等,错误只在非均匀缩放下暴露。4096 个单位法线扫描给出归一化后的模长不变量 |n| ∈ [ONE−5, ONE]。