把一张棋盘贴到倾斜平面上,外轮廓已经投影成梯形,内部格线却在三角形对角线处折弯。问题可能不在图片,也不在投影矩阵,而在投影之后仍然用屏幕重心坐标直接平均UV。

本篇用两个三角形表示同一个平面,保持几何、相机、覆盖规则和棋盘采样方式不变,只切换UV插值。除了两张结果图,还通过射线与平面相交独立求出UV,避免用同一个插值公式验证自身。

UV描述的是表面上的位置

纹理坐标u、v是从表面位置到二维纹理域的映射。本实验把平面四角分别标为(0,0)、(1,0)、(1,1)、(0,1)。u增加表示从左向右,v增加表示从平面近边走向远边。它们不是屏幕像素坐标,也不是相机距离。

棋盘在UV域分成8×8格,取floor(8u)floor(8v),两者之和的奇偶决定灰度。端点1钳制到最后一格,防止访问第8号格。这是明确选定的点采样和边界规则;本篇没有读取外部纹理图片,也没有双线性过滤。

两种灰度在线性域取0.04与0.8,最后通过第04篇的sRGB函数编码一次。因而本次比较不会混入“一个版本在线性域、另一个版本在显示码值域”的颜色差异。

屏幕中点不一定对应空间中点

先看一条边。空间端点A、B的w分别为1和3,屏幕位置分别为0和1。空间中点的齐次位置是两个端点的平均,其投影位置为:

x=(1×0+3×1)/2(1+3)/2=34.x=\frac{(1\times0+3\times1)/2}{(1+3)/2}=\frac34.

也就是说,空间参数的一半映到了屏幕边长的四分之三处。若屏幕上直接把参数线性从0插到1,就会给这个位置分配0.75,而非原来的0.5。透视除法改变了沿边参数的分布。

同样的问题发生在三角形内。屏幕重心权重能正确重建屏幕位置,却不能不加条件地重建投影前的表面属性。只有三个顶点w相同,或属性恰好特殊到两种结果一致时,直接线性插值才不会暴露差异。

从两组重心权重推导

设投影前的仿射权重为αi\alpha_i,满足αi=1\sum\alpha_i=1;屏幕权重为λi\lambda_i。齐次位置先做仿射组合再除w,可得:

λi=αiwijαjwj.\lambda_i=\frac{\alpha_i w_i}{\sum_j\alpha_jw_j}.

将它反解,并利用原权重之和为1:

αi=λi/wijλj/wj.\alpha_i=\frac{\lambda_i/w_i}{\sum_j\lambda_j/w_j}.

表面上按原权重线性变化的u,因此应写成:

u=iαiui=iλiui/wiiλi/wi.u=\sum_i\alpha_i u_i =\frac{\sum_i\lambda_i u_i/w_i}{\sum_i\lambda_i/w_i}.

v使用同一分母。这就是透视正确插值。实现可以先插值u/w、v/w与1/w,再用前两者除以后者。不能只将UV除以w而忘记最后恢复;那样得到的仍是u/w,不是u。

本系列w来自第02篇的投影矩阵,即相机空间的−z。不能在公式中随意换成世界空间z或到相机的欧氏距离。后两者与投影除法所用的w不同,会改变结果。

第06篇深度已经是z/w,它在屏幕上直接线性插值。这不与上述公式矛盾:深度的分子与分母本来就来自齐次位置的投影,若对已经插好的深度再应用一次UV公式,就多除了一次透视因子。

一个可独立手算的样本

取三个屏幕权重都为1/3,顶点w为(1,2,4),u为(0,1,0)。仿射插值得到1/3;透视正确结果为:

u=(1/3)(1/2)(1/3)(1+1/2+1/4)=27.u=\frac{(1/3)(1/2)}{(1/3)(1+1/2+1/4)}=\frac27.

检查器实际得到0.285714285714286,并与2/7比较,容差为101210^{-12}。这里没有图像采样、边界覆盖或颜色编码,可以单独定位插值公式错误。

接口拒绝零、负或非有限w,并检查输入UV与最终结果有限。它供裁剪后的可见图元使用,不接收横跨相机平面的原始边。分母非正时也拒绝计算;除一个任意小常数不会让非法几何变成合法属性。

同一倾斜平面的两张结果

相机朝−Z,竖直视场90°,宽高比1,近远距离1与10。四个相机空间顶点为:

(1,1,2),(1,1,2),(1,1,4),(1,1,4).(-1,-1,-2),\quad(1,-1,-2),\quad(1,1,-4),\quad(-1,1,-4).

它们位于平面z=3yz=-3-y。近边宽度投影较大,远边缩小。视口为256×256,四个连续屏幕位置恰好为(64,192)、(192,192)、(160,96)、(96,96),能被第05篇的1/256固定格精确表示。

全平面在视锥内,两个三角形沿第一个顶点到第三个顶点分割。覆盖仍用top-left规则,两种插值访问完全相同的样本位置。

屏幕仿射UV插值,棋盘格线在三角形连接处改变斜率

第一张是屏幕仿射插值。每个三角形内部UV是屏幕位置的仿射函数,但两片使用的函数不同;跨对角线的格线出现折弯。共享边本身并没有漏画,修覆盖规则不能修复这种属性畸变。

透视正确UV插值,倾斜棋盘保持平面的投影关系

第二张使用透视正确插值。相机中的平行格线遵守相同的投影关系,近处格子更大,远处更小。两张图均来自实际C++输出,再由sips转换PNG;放大只用于观察样本,不增加纹理细节。

两个版本共覆盖9216个样本,其中4026个棋盘颜色不同。这个数量只描述固定场景的差异,不是一个通用图像质量评分,也不表示剩余样本的UV都完全相同。两个不同的UV仍可能落在同一棋盘格,因而显示同色。

用射线和平面相交核验UV

对于像素中心(x+0.5,y+0.5)(x+0.5,y+0.5),由视口反解出NDC坐标qx,qyq_x,q_y。当前相机的视线可写成:

p(t)=(tqx,tqy,t).p(t)=(tq_x,tq_y,-t).

将它代入平面z=3yz=-3-y

t=3tqy,t=31qy.-t=-3-tq_y,\qquad t=\frac3{1-q_y}.

交点在平面上的x、y确定纹理坐标:u=(tqx+1)/2u=(tq_x+1)/2v=(tqy+1)/2v=(tq_y+1)/2。这条参考路径没有调用重心插值函数,而是根据相机射线和已知平面直接求值。

在所有被覆盖样本上比较两个UV分量,屏幕仿射版本的最大绝对误差为0.171567496924969;透视版本为2.22044604925031×10162.22044604925031\times10^{-16}。后者接近这组双精度计算的舍入量级。

这个很小的误差依赖当前投影顶点恰好落在固定格上。一般场景的光栅顶点会量化,而解析射线对应原始几何,二者之间会出现额外误差。不能拿这一次结果宣称任意模型都达到同样精度。

裁剪生成的顶点也必须携带UV

第06篇只给整个图元一个常量颜色,裁剪交点只需位置。贴图之后,新交点还要有自己的UV。沿齐次边a→b求得交点参数t时,应同时计算:

(u,v)new=(u,v)a+t((u,v)b(u,v)a).(u,v)_{new}=(u,v)_a+t((u,v)_b-(u,v)_a).

此处插值的是原始UV,不是已经除w的UV;裁剪发生在透视除法之前。交点产生后,它拥有新的w,进入光栅阶段才用这个w构造u/w、v/w和1/w。

共享裁剪循环现在接受位置四分量加后续属性。原来的clip_triangle仍调用同一循环,因此06的六平面、远面和顺序检查继续运行,没有复制第二份裁剪实现。

独立近面检查让一个顶点的裁剪z为−0.5,另两个为1,交点t=2/3。输入UV为(0,0)、(1,0)、(0.5,1),两个新UV应为(1/3,2/3)、(2/3,2/3)。检查器确认两个交点与这些常数一致。这是属性裁剪的数值验收;前面的棋盘图本身未穿越裁面,不能替代这项检查。

插值正确仍可能混叠

远处一块棋盘格可能投影得比一个像素还小。正确UV只是给出了采样位置,并未计算像素覆盖的纹理区域;点采样仍可能错过细格或在移动时闪烁。第08篇再比较纹理预滤波与多重采样,避免把两个问题混成“图片不清楚”。

运行累计检查:

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

结果为uv-affine.ppmuv-perspective.ppm。原始日志在writing-plans/computer-graphics/evidence/07-cpu.txt,没有随机数、性能计时或GPU执行结论。

练习一:若三个w都等于2,透视公式是否仍不同于屏幕仿射插值?若只把所有w同时乘10,UV会变化吗?

答案:三个w相等时公共因子约掉,退化为屏幕仿射插值。所有w同时乘10也会使分子分母同时缩小10倍,结果不变。这两个性质可用于发现漏掉分母或错误使用距离的实现。

练习二:为什么4026个颜色不同的样本不能代替UV误差检查?把棋盘改成全白会怎样?

答案:纹理颜色是UV的函数,不一定一一对应。同一个格内大量不同UV都返回同色;全白纹理会让两个版本的颜色完全相同,但几何插值错误仍存在。因此应保留独立连续UV参考,再把图像作为现象说明。

系列导航与资料

前篇:06:哪个表面可见系列入口。下一篇:08:怎样减少锯齿与纹理闪烁

Olano与Greer:Triangle Scan Conversion Using 2D Homogeneous Coordinates,§4.1式3、§4.2与§6,齐次插值和纹理坐标的处理。原始阅读记录见writing-plans/computer-graphics/evidence/research-05-08.md,核验日期2026-09-19。本篇公式按系列−Z相机重新推导。

实现入口为examples/computer-graphics/texture.hpptexture_check.cpp与共享visibility.hpp。本篇使用CPU明确选择的规则,不将结果表述为已经通过WebGPU验证。