14.3 中间缓冲的可视化
四张调试缓冲不该各写各的导出代码。本节把它们收进一个统一导出器:每张图写出 8 位 PNG、一份 sha256、一条「回读像素 == 内存缓冲」断言,再用纯 xml.etree 拼一张内嵌 base64 的 SVG 联系表,最后用 ET.fromstring 回读校验结构与内嵌字节。
category
四张调试缓冲不该各写各的导出代码。本节把它们收进一个统一导出器:每张图写出 8 位 PNG、一份 sha256、一条「回读像素 == 内存缓冲」断言,再用纯 xml.etree 拼一张内嵌 base64 的 SVG 联系表,最后用 ET.fromstring 回读校验结构与内嵌字节。
二值覆盖掩码只回答「涂过没有」,调试要问「涂了几次」。本节把覆盖率存成整数计数,用双重计数与二值化基线两条不变量把它钉住;线框复用第 7 章的整数 Bresenham 只描正面三角形,并在浏览器里用加法混合让 GPU 独立算出同一份计数,再逐像素对齐。
深度缓冲不是距离,直接 gray = z*255/ONE 会把整帧压进 20 个灰度级、闲置 236 级。本节先把这个浪费量化,再用按距离等分的 8x32 分段映射把每级钉在 0.157 世界单位;法线图由插值后的顶点法线编码进 RGB,两条负例必须被模长判据拒绝。
画家算法不逐像素做深度测试,只把三角形按质心深度从远到近画、后画者胜。本节在可分离场景里让画家与 z-buffer 逐字节相同,在相交场景里让它们出现 63 个差异像素,并说清差异的来源是质心排序在交叉处判错了局部前后。脚本用「深度 = 绘制序号的相反数」把画家算法搭在共享光栅化器上。
只裁近平面时投影坐标会飞出视口 64 倍,整数包围盒加剪刀矩形把扫描窗口压回视口且逐字节不改像素;保护带 G=0 就能让 80.7% 的三角形免裁,G=256 提到 93.4%。本节实测原始包围盒 4922869 单元、夹紧后 244708、扫描 223753 单元覆盖 43730 像素。
近平面裁剪会把三角形裁成四边形甚至五边形,重建它靠的是扇形三角化的整数恒等式:三个扇形三角形的面积和精确等于多边形面积,逆序则精确变号。本节实测英雄三角形 3→4→5 的逐步计数、面积比恰为 t^2 = 1/4 的解析解,以及「丢弃 vs 裁剪」两帧覆盖异或为 0 而颜色差 171。
视锥六个面在视图空间解析写成四条整数系数,与投影矩阵同源;Sutherland–Hodgman 的交点用 fdiv 定标、算术右移降精度,于是面积守恒成为精确整数恒等式。本节实测六平面系数、487 个顶点的分类、英雄三角形 3 裁成 5 的逐步计数,以及裁剪前后两帧覆盖异或为 0。
两个几乎重合的面若在深度缓冲里落进同一格,深度测试就分不出前后,像素随视角抖动——这就是深度冲突。本节用两块相距 13/65536 的平行墙,把朴素线性 z、标准透视 z、逆转 z 三种映射的冲突像素数摆在一起,解释逆转 z 为何要把深度测试从 < 翻成 >,并如实说明定点缓冲与浮点缓冲下它的差别。
线宽不能用开方算距离:把 d <= r 两边平方,得到一条纯整数的覆盖判据 cross^2 <= r^2*|AB|^2,端帽用最近端点的距离平方兜底。本节实测五个线宽的覆盖数、水平与竖直的对称性,并用 N x N 子样本把覆盖率收敛到解析面积 194514 千分像素^2。
半像素误差界可以写成一条不含除法的整数判据:2*|k*major - i*minor| <= major。本节在 89439 个采样点上穷举验证:Bresenham 与半像素 DDA 的实测 max|e| 恰好是 0.5 像素,floor 取整的 DDA 有 1922 组越界;界在 651 组上被恰好达到,说明它已不能再收紧。
《计算机图形学》卷一附录 A:scene_spec.json 的 71 个字段全表(相机、方向光、点光源、地形、球体、渲染参数,全部为整数)、Q16.16 与 Q6 子像素的定点约定、四条舍入铁律、定点不变量清单、两条一致性纪律的判据表,以及全书数据集索引与一个可跑的定点约定自检脚本。
开曲面没有厚度:从背面看,一块 4x4 的布帘 32 个三角形全是背面,单面渲染得到一张空图。双面材质的做法不是不剔除,而是把背面三角形的几何法线取反再着色;本节实测单趟与两遍渲染逐字节相同,并给出「忘了取反」的指纹——背面图像恰好是正面图像的左右镜像。
背面剔除的判定只是一次整数符号比较,可它省下的到底是什么?本节把代价拆成像素测试数与覆盖像素数两笔,实测 896 个三角形下测试数从 161448 降到 140493(省 129‰),覆盖集合分毫不变,而地形这类开曲面在掠射处仍有 2477 个像素会因剔除而换色。
屏幕空间边函数 E(A,B,C) 的符号就是三角形的朝向。本节在《烛影山谷》的 896 个三角形上实测 606 正面、290 背面、0 退化,与光栅化器剔除的 290 完全一致;再证明世界空间几何法线与视线方向的点积是同一条判据,并用 det<0 的镜像矩阵说明手性反转时法线必须跟着翻。
直线的光栅化先要把方向折进一个八分圆,再决定每一步的取整语义:floor 会系统性偏下并漏掉终点,半像素偏置才落到最近像素。本节用定点 DDA 与整数 Bresenham 扫过 2079 组斜率,量出两者的分歧只出现在平局且步长无截断的 129 组,并用覆盖掩码的 sha256 把 Python 与 TypeScript 逐字节对齐。
多个三角形覆盖同一个像素时,谁的颜色留下由深度测试决定。本节把 6.2 插出的深度送进 z-buffer,说清严格 `<` 与 `<=` 的差别:前者让共享边像素的归属由绘制顺序决定——这不是「与顺序无关」,只是把赢家换成先画的那个;再用一份 64×48 场景让 WebGL2 与 CPU 定点内核对照,并给出必被拒绝的负例。
把 6.1 的三条边函数归一化就得到重心坐标:权重 w0/w1/w2 是像素对三个顶点的贡献比例。本节证明不变量 w0+w1+w2 恒等于面积(定点残差严格为 0),用权重线性插值颜色与深度,说清分子非负时 Python 的 `//` 与截断除同义,最后让 TypeScript 复现出逐字节相同的像素缓冲。
从整数边函数 E(A,B,P) 的「有向面积 2 倍」含义出发,推出三个边函数同号即在三角形内;实测屏幕空间 y 向下时 area > 0 对应的环绕方向、Q6 子像素精度与像素中心取法,并用子像素扰动实验区分恒等实现,最后看 `>= 0` 如何让共享边像素被重复认领。
视锥六平面的解析构造与近远平面:从 MVP 直接抽出六平面并用 45 个采样点核对判定,解释近平面不能取 0(深度值会塌到 1),用纯 xml.etree 生成 depth(D)=far/(far-near)·(1-near/D) 的精度分布 SVG,量化给出首个倍程独占约 503‰ 深度量程的结论。
逐项推导透视与正交两个投影矩阵:透视最后一行 (0,0,-1,0) 让 w=-z_view 随深度变化、片元要除以 w,正交最后一行 (0,0,0,1) 让 w 恒为 1、不做透视除法。用同一场景分别渲染两帧并量化覆盖率与深度分布差别,说明正交为何在工程制图里仍然有用。