斜边变得平滑以后,地板上的细棋盘仍可能随着相机移动而闪烁。这两种现象都涉及采样,但需要平均的对象不同:轮廓处要估计像素有多少面积被图元覆盖;图元内部则要估计一个像素对应的纹理区域里有哪些颜色。

第07篇已经修正透视UV插值。本篇继续区分“采样位置正确”与“采样区域充分”,用两个互相独立的CPU实验验证覆盖采样和纹理预滤波。它们采用明确的规则网格,不代表某块GPU的实际多采样布局。

一个像素需要的是区域信息

先选盒式像素滤波器,把像素看成单位正方形。对黑色背景上的白色图元,理想线性颜色等于图元与像素相交的面积,范围为[0,1]。若恰好覆盖四分之一,颜色应为0.25,再经过sRGB编码用于显示。

在像素中心只问一次“是否在三角形内”,只能得到0或1。沿斜边移动时,这个判断会突然翻转。增加覆盖样本可以得到更多中间值,但有限点集仍是面积的近似,不等于精确积分。

本实验的64×64视口中,白色区域在直线y=0.375x+8.25y=0.375x+8.25上方,屏幕Y仍向下。它由两个三角形组成,四角为(0,0)、(64,0)、(64,32.25)、(0,8.25),继续使用第05篇的固定格与top-left覆盖规则。

每个像素分别放置1×1、2×2、8×8个规则中心样本。2×2的偏移为(0.25,0.25)、(0.75,0.25)、(0.25,0.75)、(0.75,0.75)。所有坐标可由1/256固定格精确表示,因此不会把不同量化误差混入比较。

用解析面积检查覆盖结果

像素左上角为整数(x,y),横向局部坐标为s,取值[0,1]。像素内的白色高度是:

h(s)=clamp(0.375(x+s)+8.25y,0,1).h(s)=\operatorname{clamp}(0.375(x+s)+8.25-y,0,1).

覆盖面积为A=01h(s)dsA=\int_0^1h(s)ds。令a=0.375x+8.25ya=0.375x+8.25-y,并定义:

F(t)={0,t0,t2/2,0<t<1,t1/2,t1.F(t)=\begin{cases} 0,&t\le0,\\ t^2/2,&0<t<1,\\ t-1/2,&t\ge1. \end{cases}

由于F(t)=clamp(t,0,1)F'(t)=\operatorname{clamp}(t,0,1),可以直接得到A=[F(a+0.375)F(a)]/0.375A=[F(a+0.375)-F(a)]/0.375。这个参考路径不调用三角形覆盖函数,也不靠更密的采样近似自身。

实际输出的线性覆盖误差如下。平均绝对误差除以整个视口4096个像素;多数像素远离边缘,误差为零,所以同时保留最大误差。

每像素样本 平均绝对误差 最大绝对误差
1 0.00406901041667 0.4375
4 0.00113932291667 0.0833333333333
64 0.0000406901041667 0.00520833333333
每像素单点覆盖,斜边呈阶梯 每像素四点覆盖,斜边出现中间灰度 每像素六十四点覆盖,更接近解析面积

三张图来自实际程序的64×64 PPM,经sips转换PNG,按最近邻方式放大。颜色平均发生在线性域,最后只编码一次。不能从PNG码值直接计算上表的面积误差,也不能把当前斜率的结果当成所有轮廓的误差上界。

SSAA和MSAA分别重复什么

SSAA在多个子样本位置计算覆盖和着色,再合并成一个像素。纹理坐标、纹理查询以及其他着色计算都可能重复。MSAA通常为多个样本保存覆盖与深度,却允许一个图元在像素内只执行一次着色,把结果写给该图元覆盖的样本。

对白色常量图元,两种方法在使用相同覆盖点和合并权重时得到相同结果:每个被覆盖样本都是白色,重复计算颜色没有新增信息。前面的实验只检查这一覆盖积分问题,没有测量GPU着色次数或性能。

若一个图元完全覆盖像素,而其内部纹理颜色变化很快,一次着色仍只提供一个颜色。将它复制到四个样本再平均,结果仍是原值。普通的逐像素着色MSAA因此不能保证解决纹理缩小混叠。实际API还支持逐样本着色等选择,不能把“一次着色”当成MSAA永远不可改变的定义。

覆盖、深度和着色也不总在同一个位置计算。这里固定规则中心位置,只解释最小差别;GPU篇会按实际API和硬件行为验收,当前CPU结果不计作GPU验证。

双线性过滤为什么仍会闪烁

考虑256×256的黑白纹理,每个纹素与上下左右邻居异色,线性值为0或1,边界周期重复。一个屏幕像素覆盖4×4纹素大小的正方形。这个区域在两个方向都覆盖完整的周期,无论平移多少,盒式平均恒为0.5。

基础层双线性过滤只读取查询点附近四个纹素,并按距离加权。它解决了相邻纹素间的连续过渡,却没有因为像素覆盖范围变成4×4而自动扩大滤波范围。连续变化的错误颜色仍是错误积分。

纹素中心采用(i+0.5)/256(i+0.5)/256。程序以纹素单位选择查询中心(2.25+ϕ,2.25)(2.25+\phi,2.25),令相位ϕ\phi从0到0.875,每次增加0.125。这个平移改变纹理与像素的相对位置,不改变足迹面积。

八个基础层双线性结果依次为0.375、0.3125、0.25、0.3125、0.375、0.4375、0.5、0.5625。峰谷差为0.3125,而正确区域平均始终是0.5。如果把这些数按时间显示,同一像素会改变明暗;以下图像把八个时刻并列成八条竖带,便于静态核查。竖带并不是纹理空间中真实存在的条纹。

八个移动相位的基础层双线性结果,每条竖带代表一个时刻

这个场景也构成少量规则SSAA的反例。2×2子样本在纹理中相隔2个纹素,恰好重复同一个周期相位,四点平均仍等于上面的错误值。8×8子样本间隔0.5纹素,本次八个相位都得到0.5。增加样本有效与否还取决于信号和布局,不能只看样本数。

检查器同时输出逐像素着色MSAA4的CPU模型值,即把中心颜色复制四次后的平均。它与基础层结果相同。这是模型的数值对照,没有声称调用了硬件MSAA。

Mipmap提前保存不同范围的平均

Mipmap把原纹理作为第0层,每次将2×2纹素在线性域平均成一个纹素。256平方的纹理依次变成128平方、64平方,直到1平方。第L层一个纹素代表基础层约2L×2L2^L\times2^L的区域。

当前黑白纹理的每个2×2块都有两个0和两个1,所以第1层及以后全为0.5。选择第2层对应4×4基础纹素的范围,八个相位实际都输出0.5,最大绝对误差为0。

相同八个相位使用Mipmap第二层,线性平均始终为0.5

这个精确结果来自特意选择的周期棋盘,不表示Mipmap能精确积分任意像素足迹。一般纹理在层内仍需要插值,像素足迹可能旋转、拉长或弯曲,预存正方形平均只能近似。实验的作用是把基础层小范围插值和大范围预滤波明确分开。

本实现mipmap.hpp只接受不超过4096的正方形、二次幂尺寸以及[0,1]线性标量,采用repeat寻址。这样能用很少代码验证滤波,不隐含支持任意尺寸、彩色文件解码或GPU纹理格式。RGB可以逐通道使用相同线性运算,但sRGB文件应先解码再生成平均值。

层级选择与三线性过滤

对于尺寸W×H的纹理,若局部映射的屏幕导数已知,一种教学用尺度估计是:

ρ=max((Wux)2+(Hvx)2,(Wuy)2+(Hvy)2),L=clamp(log2max(ρ,1),0,Lmax).\rho=\max\left(\sqrt{(Wu_x)^2+(Hv_x)^2}, \sqrt{(Wu_y)^2+(Hv_y)^2}\right),\qquad L=\operatorname{clamp}(\log_2\max(\rho,1),0,L_{max}).

这里的x、y以屏幕像素为单位。每移动一个像素跨过4个纹素,则ρ=4,L=2\rho=4,L=2,正是本实验的选择。程序直接传入由固定映射得到的L=2,没有实现透视表面的自动导数估计,也没有把这个公式说成PBRT的原样实现。

当L=2.3时,可以在第2层和第3层分别双线性查询,按0.7与0.3混合,这称为三线性过滤。第三次线性插值发生在层级方向,不是读取三维纹理。本次代码用L=0.5检查两层结果的中间值,并检查常量纹理在不同LOD、负UV和越界LOD钳制后保持常量。

各向同性层级只有一个尺度。当斜视表面的足迹一个方向跨16纹素、另一个方向只跨1纹素,按最大尺度选层会在短方向过度模糊。各向异性过滤或EWA需要进一步利用足迹形状。本篇没有实现这些算法,也不将当前正方形实验推广成倾斜地面的完整过滤器。

复跑、练习与自检

在仓库根目录执行:

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

输出目录须存在。结果包含三张覆盖PPM、两张相位PPM和控制台数值;累计检查还复跑00–07。日志为writing-plans/computer-graphics/evidence/08-cpu.txt。实验没有随机数,覆盖固定1、4、64点,纹理固定八个相位与4、64点,不使用自适应终止,也没有性能数据。

练习一:一个常量白色图元覆盖四个MSAA样本中的一个,背景为黑色。线性合并值是多少?先把白黑sRGB码值平均再当作线性值,为什么不对?

答案:线性值为1/4。输出sRGB编码约0.5371,即8位码约137。码值直接平均得到约64,显示会明显偏暗。样本合并与颜色编码的顺序不能互换。

练习二:纹理映射在x方向每像素跨8纹素,y方向跨2纹素,上面的尺度公式选哪一层?为什么它可能模糊?

答案:ρ=8,L=3\rho=8,L=3。第3层约平均8×8基础纹素,而短方向实际只需要跨2纹素,额外平均损失了短方向细节。MSAA增加覆盖点不会自动修正这个足迹形状问题;需要更合适的纹理过滤。

系列导航与资料

前篇:07:为什么贴图会扭曲系列入口。下一篇:09:法线怎样决定明暗

Microsoft:Rasterization Rules,Multisample Anti-Aliasing Rasterization Rules,核对覆盖、深度与着色频率的区别。此处引用概念,不将Direct3D的具体规则冒充WebGPU规范。

PBRT 4ed §10.4 Image Texture,10.4.3–10.4.4及图10.16,核对MIPMap与图像纹理过滤。资料核验记录在evidence/research-05-08.md;本文的斜边积分、周期棋盘和确定性实验独立构造。