← ALL ARTICLES

光栅化与属性插值

全库目录221
本文目录9

同一个三角形的UV在斜面上扭曲,往往是屏幕插值没有还原透视关系。光栅器先判断覆盖,再插值顶点属性,重心权重与clip w各有用途。我们从共享边覆盖开始,再看透视校正与导数,利用棋盘格区分坐标错误和过滤误差。

三角形覆盖

经过裁剪和透视除法后,三角形落到屏幕坐标。光栅器根据边函数判断一个样本点位于三角形内部、外部还是边界。

对边从 AA 指向 BB,定义 EAB(P)=(Bx−Ax)(Py−Ay)−(By−Ay)(Px−Ax)E_{AB}(P)=(B_x-A_x)(P_y-A_y)-(B_y-A_y)(P_x-A_x)。它是有向面积的两倍,符号表示点位于边的哪一侧。对逆时针三角形,三个边函数都为正的点在内部;除以整三角形的有向面积就得到对应重心权重。屏幕Y轴向下时绕序符号要同步改变。面积为零的退化三角形不能做这次除法。

共享边需要统一规则,否则两个三角形可能同时覆盖或同时漏掉边界样本。常见实现使用 Top-left Rule,但具体定义由 API 规范决定。

共享边样本的归属

以屏幕Y向上、逆时针边界作为教学约定,边函数大于零表示内部。边函数恰好为零时,需要半开边界规则。例如将向下的边,以及向左的水平边标为包含边:dy<0 || (dy==0 && dx<0)。这对应此坐标约定的上边/左边归属;API若使用向下Y和不同绕序,判断式必须随之变化。

正方形A=(0,0)、B=(2,0)、C=(2,2)、D=(0,2)拆成ABC与ACD。共享边在第一片中为C→A,dy=-2,包含边上样本(1,1);第二片的A→C,dy=2,不包含该样本。两片内部都能覆盖自己的区域,共享边却只由一片拥有。若对全部零值都接受,会重复混合;全部拒绝则留下裂缝。实际光栅器还在规定的子像素精度和采样位置上计算覆盖,本例用于解释归属规则。

重心坐标

三角形内的点可以表示为三个顶点的加权和:

P=λ0P0+λ1P1+λ2P2,λ0+λ1+λ2=1P=\lambda_0P_0+\lambda_1P_1+\lambda_2P_2, \qquad \lambda_0+\lambda_1+\lambda_2=1

三个 λ\lambda 就是重心坐标。光栅器用它插值颜色、UV、法线等 Vertex Shader 输出。

透视正确插值

透视投影后,屏幕距离和三维距离不再线性对应。如果直接在屏幕空间线性插值 UV,纹理会在倾斜表面上滑动或扭曲。

透视正确插值会先插值 ai/wia_i/w_i 和 1/wi1/w_i:

a(P)=∑iλi(ai/wi)∑iλi(1/wi)a(P)= \frac{\sum_i\lambda_i(a_i/w_i)} {\sum_i\lambda_i(1/w_i)}

取一条边的屏幕中点,两个端点的 ww 为1和2,属性值为0和1。屏幕平均得到0.5,透视插值则为 (0/1+1/2)/(1/1+1/2)=1/3(0/1+1/2)/(1/1+1/2)=1/3。远端在屏幕上被压缩,屏幕中点并不是原始线段的参数中点。这也是倾斜棋盘格需要校正的原因。

Shader 语言通常默认对普通 Varying 使用透视正确插值。noperspective、flat/nointerpolation 等修饰符会改变行为。

屏幕空间导数

Pixel Shader 常按 2x2 Quad 一起执行。ddx 和 ddy 可以用相邻 Lane 的值估计屏幕方向变化。纹理系统据此估计 Mipmap LOD。

控制流发散、三角形边缘和 Helper Invocation 会让导数行为更复杂。不要在不一致分支中假设导数一定稳定。

取一个2×2像素组,上排属性值为(2,4),下排为(5,9)。按行作差,上排ddx为2,下排为4;按列作差,左列ddy为3,右列为5。导数反映邻近调用的属性变化,不是把当前值代入一个解析求导器。三角形边外的辅助调用可补足差分,但没有覆盖的调用并不输出同样的颜色或副作用;在只有部分调用执行的分支里,也不能假设所需邻居都提供了相同定义的值。具体fine/coarse导数选择按着色语言约定。

透视插值的计算轮廓

输入是屏幕空间重心权重、三个顶点的 UV 和裁剪坐标 w,输出当前采样点的 UV。裁剪已经完成,w 非零,权重之和为 1。

hlsl
float2 InterpolateUv(float3 weights, float3 clipW,
                      float2 firstUv, float2 secondUv, float2 thirdUv)
{
    float3 corrected = weights / clipW;
    float denominator = corrected.x + corrected.y + corrected.z;
    float2 numerator = corrected.x * firstUv
                     + corrected.y * secondUv
                     + corrected.z * thirdUv;
    return numerator / denominator;
}

这是光栅器默认透视插值的数学轮廓,普通片元 Shader 无需手写重复计算。将三个 w 设为相同,结果退化为屏幕线性插值;w 不同时用倾斜棋盘格比较两种结果。

验证方法

  • 用共享边三角形检查漏缝与重复覆盖。
  • 在倾斜棋盘格平面上对比透视正确和屏幕线性插值。
  • 输出 UV 导数及 mip 等级,检查三角形边界和不一致分支。

相关主题

实现与验证:raster_models.py 与 test_raster.py检查透视中点反例及相同w的退化情形,运行 python -B 知识库/examples/revision/test_raster.py。正文HLSL是数学片段,完整光栅器覆盖、导数及GPU画面尚未运行。

参考资料

  • Microsoft Direct3D 11 Rasterization Rules:2026-10-22核读Triangle Rasterization Rules和Multisample Anti-Aliasing,支持共享边、样本覆盖和2×2导数执行说明。
  • 透视插值数值例由本页公式及CPU模型独立复算。