立方体的位置、轮廓与阴影都正常,一个原本背光的侧面却出现了棋盘。检查最终颜色只能知道结果变了,无法立即判断是纹理、法线、光方向还是颜色编码出了问题。把同一个可见样本分别画成深度、法线和UV,能缩小排查范围;再用数值检查验证阶段之间的关系,才能确认原因。

本篇复用第10篇的地面、立方体、相机与光源,不再复制一套渲染器。正确路径输出四张诊断图,错误路径只在照明时混用局部法线与世界光方向。几何、纹理查询和阴影比较全部相同,因此图像差异有一个明确来源。

中间结果需要有明确含义

当前累计管线先变换顶点,在齐次空间裁剪,随后除w、映射视口、判断覆盖并比较深度。通过测试的样本保存局部位置、面法线和对象编号。着色阶段据此求UV、查询纹理与阴影,在线性域计算明暗,最后编码为sRGB。

这些阶段对应常见图形管线的变换、光栅化、逐样本着色与可见性职责。但这里的执行次序和数据结构属于CPU教学实现,不描述GPU一定采用的内部调度,也没有实现全部可编程阶段。

test_scene.hpp从第10篇提取原有场景提交代码,仍是地面两片、盒子十二片三角形。shadow_check.cpp与新增的pipeline_check.cpp调用同一个函数,使用已有DepthImageSurfaceBuffer。提取后复跑第10篇,三档偏移的FP、FN与区域计数均保持不变。

场景采用的局部到世界变换是正交旋转,所以法线可乘相同旋转。非均匀缩放仍须用第09篇的逆转置。本实验没有因为这个特例方便,就删除累计检查里的非均匀缩放和剪切反例。

四张图共享同一份可见表面

相机输出512×512,阴影图64×64,接收点偏移固定0.01。黑色背景表示没有写入表面。四图从同一个SurfaceBuffer生成,不分别运行不同的覆盖或深度逻辑,否则诊断图可能在分析另一张图像。

颜色图给地面与盒子各面添加棋盘。基础纹理64×64,每8个纹素换色,线性灰度为0.2和0.8,采用第08篇的repeat双线性查询。这里固定第0层,没有自动估计纹理足迹或LOD,也没有开启SSAA。它用于集成检查,不宣称消除了远处混叠。

共享场景的正确颜色图,棋盘地面与贴地立方体带投影阴影

线性照明值采用:

C=0.05+V0.7T(u,v)max(0,nl),C=0.05+V\,0.7\,T(u,v)\max(0,n\cdot l),

其中V为二值光源可见性,受光为1、阴影为0,T是线性纹理灰度。最后三个通道都编码同一个C,得到灰度图。底色0.05是固定教学参数;当前没有使用高光项,也不把这条直接光公式称为完整物理照明。

深度图直接将相机NDC深度乘255并四舍五入,写入RGB三个通道:

相机NDC深度诊断图,近处较暗,远处较亮,背景黑色

近处小、远处大,因此地面呈连续灰度变化,盒子的可见表面也有自己的深度。背景黑色来自对象编号为0时跳过输出,并不代表背景深度等于近面。底层深度缓冲仍清为1。若要从图中恢复精确深度,8位量化已经不够,应读浮点缓冲或数值日志。

世界法线图将单位法线各分量从[-1,1]映射到[0,1]:

(R,G,B)=round(255n+(1,1,1)2).(R,G,B)=\operatorname{round}\left(255\frac{n+(1,1,1)}2\right).

世界空间法线诊断图,地面与盒顶同色,两个侧面不同色

地面与盒顶的法线同向,所以颜色相同,即使它们的高度与照明不同。两个侧面颜色不同,边缘突变符合盒子的硬边几何。本篇使用面法线;不能把这种突变误诊为第09篇平滑法线插值失败。

UV图的接缝是否属于错误

地面局部范围x∈[-4,4]、z∈[-3,3],定义u=(x+4)/8、v=(z+3)/6。盒子每个面单独映射:x法向面取(z+0.5,y),y法向面取(x+0.5,z+0.5),z法向面取(x+0.5,y)。这是一组明确的逐面平面映射,没有外部模型文件或展开工具。

UV由插值后的局部位置计算。每个面上的映射都是仿射函数,因此与先给顶点赋UV、再用相同透视权重插值等价:仿射函数可与权重和为1的加权和交换。这个等价只适用于当前逐面仿射映射,不适用于任意非线性程序纹理坐标。

UV诊断图,红通道为u绿通道为v,盒子不同面允许接缝

红通道表示u,绿通道表示v,蓝通道固定0。地面应有连续梯度,三角形内部对角线不应产生跳变;盒子换面时允许重新开始,因此面边缘的接缝是当前参数化的结果。若不先说明UV定义,看到接缝就修改插值可能破坏正确实现。

深度、法线与UV图都直接写诊断码值,没有再经过sRGB编码。它们是数值的可视化标签,不是线性辐射亮度。浏览器显示出来的亮度仅帮助观察,不能将截图通道当作原始浮点值计算误差。颜色图则必须编码一次,这两种输出用途不同。

用重投影检查位置是否仍对应这个像素

保存的位置应当属于当前样本。将它再次转世界坐标、乘相机矩阵并映射视口,应该回到像素中心附近;将其NDC深度与保存深度比较,也应一致。

但第05篇先把屏幕顶点量化到1/256像素格,插值权重基于量化后几何。重投影使用原始顶点所定义的几何,不能要求横纵坐标严格相等。本场景正交投影且覆盖内权重非负,重投影偏差是三个顶点量化误差的加权和,每轴界限为半格,即1/512像素。

实际最大横纵误差为0.00181899662194951像素,小于0.001953125的半格界限;深度最大误差为1.11022302462516×10⁻¹⁶。这里的界限依赖正交、相同权重和当前量化规则,不能直接推广到任意透视场景。

最终覆盖68,584个样本,其中地面64,147个,盒子4,437个。地面被阴影图判暗2,889个,恰等于第10篇同一偏移下2,866个真阳性加23个假阳性。这个相等确认集成没有悄悄换场景或偏移,并没有消除那23个已知边界误判。

所有可见法线的最大单位长度误差为0。这个值来自当前轴向面法线经同一正交变换后的有限样本,不承诺一般模型浮点误差也为零。UV输出同时检查有限性与[0,1]范围,仅容许10⁻¹²量级的舍入。

一个看起来合理的错误路径

现在保持可见表面不变,只把照明调用里的世界法线替换成局部法线,光方向仍在世界空间。两个向量各自都是单位长度,所以“检查normalize是否执行”不会发现错误。

故意混用局部法线与世界光方向,原本背光的侧面出现棋盘

世界光方向由局部(2,1,0)/√5旋转得到,带有正的世界z分量。盒子的局部+z法线若直接与它点积,就得到错误的正余弦;正确做法将两者一起旋转,原先为零的点积仍为零。错误图因此在侧面露出棋盘,容易被误认为贴图偶然改善了细节。

实际38,811个样本的8位RGB与正确图不同,漫反射因子最大绝对差为0.108465228909328。两张图共用深度、UV、纹理值与阴影判断,故这些阶段无法解释差异。比较同一坐标空间中的点积,才直接定位了错误。

这里错误路径只生成对照图,没有替换默认正确计算。颜色不同的像素数受量化与阴影影响,不等于所有法线有误的样本数;部分错误余弦仍可能编码成相同RGB,被遮挡样本也只显示相同底色。

哪项检查回答哪种疑问

累计运行不是仅判断程序有没有崩溃。05检查共享边576个位置的唯一归属;06检查六平面裁剪、近面交点、提交顺序与远面严格比较;07用独立射线平面参考检查透视UV;08用解析面积与已知纹理平均检查采样;09检查法线正交与手算照明;10检查独立遮挡参考与偏移两类错误。11把中间位置重新投影,并核对共享场景计数。

若深度图已经错误,应先查变换、裁剪和可见性;深度正常而UV错误,再查属性和映射;二者正常但颜色错误,继续比较法线、光方向、纹理值和输出编码。这样的顺序减少无关修改,但每一步仍需要其数值证据,不能用肉眼正常作为通过条件。

当前正交场景的w全为1,无法暴露漏掉1/w的错误,所以第07篇透视检查必须继续执行。完整图像与有针对性的微小反例各有用途,不能因集成图看起来正确而删掉后者。

1
2
make -C examples/computer-graphics check
examples/computer-graphics/build/pipeline_check examples/computer-graphics/build

输出四张诊断PPM和一张错误空间对照PPM,正文PNG从真实输出转换。原始数值在writing-plans/computer-graphics/evidence/11-cpu.txt。运行没有随机数或性能计时,处理完固定场景即结束;这些CPU证据不计作GPU验证。

练习与自检

练习一:法线长度全部为1,为什么仍不能证明照明正确?本篇错误路径为什么通过长度检查却得到不同颜色?

答案:长度没有表示坐标空间。局部法线与世界光方向属于不同基底,点积不对应真实夹角。将二者变换到同一空间,或都保留在局部空间,才能保持旋转前后的点积。

练习二:若将UV图改成纯白纹理的颜色图,能否继续观察三角形对角线上的UV错误?为什么不能把0.001819像素的重投影差直接判为失败?

答案:纯白纹理对任何UV都返回相同颜色,丢掉了诊断信息。重投影差还包含固定格量化;本次在正交条件下有每轴1/512像素的推导上界,应与这个界限比较。若误差超过上界,才需要检查插值权重、坐标变换或像素中心约定。

系列导航与资料

前篇:10:光源看不见的地方怎样变暗系列入口。下一篇:12:三角网格怎样保存邻接

Microsoft:Graphics pipeline,Vertex shader、Rasterizer、Pixel shader、Output-merger阶段说明,2026-09-20再次查阅,用于核对职责边界。公式、具体场景与错误注入由本系列独立构造;各算法来源与逐篇验收见05–10正文及evidence/research-05-08.mdevidence/research-09-11.md