中EN
概念页

硬件光栅化与透视校正插值

Hardware Rasterization & Perspective-Correct Interpolation

GPU 光栅化由三角形设置与三角形遍历组成。硬件用每条边一个边函数判断采样点是否在三角形内,利用其增量性质按瓦片分层遍历,用左上规则决定恰好落在边上的像素归属,用保护带避免大多数裁剪。边函数的值恰好是子三角形面积的两倍,因此遍历顺带得到重心坐标;透视投影下需要线性插值 a/wa/w 与 1/w1/w 再逐像素相除,得到透视校正插值。本页还介绍 2×2 quad 与辅助像素、保守光栅化。

屏幕坐标与 quad(23.1 节)

像素中心为 (x+0.5,y+0.5)(x+0.5,y+0.5)。顶点经变换(含投影、不含除以 w)得 qi=Mvi\mathbf{q}_i=\mathbf{M}\mathbf{v}_i,屏幕坐标 pi=((qix/qiw+1)W/2,  (qiy/qiw+1)H/2)\mathbf{p}_i=\big((q_{ix}/q_{iw}+1)W/2,\;(q_{iy}/q_{iw}+1)H/2\big)。像素网格分为 2×2 的 quad:为了用有限差分求纹理 LOD 所需的导数,只要 quad 中有一个像素在三角形内,整个 quad 都要做像素着色,其中在三角形外的叫辅助像素 (helper pixel)。三角形越小辅助像素占比越大,最坏情况是只覆盖一个像素的三角形需要 3 个辅助像素,这称为 quad 过度着色。这是几乎所有 GPU 的核心设计。

边函数

对过 p0\mathbf{p}_0、p1\mathbf{p}_1 的边,法线取边向量逆时针旋转 90 度 n2=(−(p1y−p0y), p1x−p0x)\mathbf{n}_2=(-(p_{1y}-p_{0y}),\,p_{1x}-p_{0x})(指向三角形内),

e2(x,y)=−(p1y−p0y)(x−p0x)+(p1x−p0x)(y−p0y)=a2x+b2y+c2e_2(x,y)=-(p_{1y}-p_{0y})(x-p_{0x})+(p_{1x}-p_{0x})(y-p_{0y})=a_2x+b_2y+c_2

e>0e>0 为正半空间。采样点在三角形内或边上当且仅当三条边都有 ei(x,y)≥0e_i(x,y)\ge0。

  • 定点化:API 通常要求屏幕顶点坐标转为定点数(如 1.14.8 位:1 位符号、14 位整数、8 位亚像素,即每像素每方向 282^8 个位置),以便一致地定义平局规则,也让内部测试更高效;在计算边方程之前完成吸附。
  • 增量性:e(x+1,y)=e(x,y)+ae(x+1,y)=e(x,y)+a,y 方向类似,可快速对小瓦片(如 8×8)"盖章"出每像素一位的覆盖掩码。

左上规则(DirectX):三条边都 ei>0e_i>0 的像素总在内部;中心恰在边上时,只有当该边是上边(水平且其余边在其下方,a=0a=0 且 b<0b<0)或左边(非水平且在三角形左侧,a>0a>0,一个三角形最多两条左边)时才算内部。这样共享边上的像素只属于一个三角形,避免被写两次。线段可当作一像素宽的长矩形(两个三角形或加一条边方程),点画成四边形,从而复用同一套边方程硬件。

分层遍历与三角形设置

  1. 计算屏幕包围盒,确定与三角形重叠的瓦片。瓦片-边测试是 AABB-平面测试的二维版本:对每条边只需测由边法线决定的那个最"内"的瓦片角(对所有瓦片相同,可在遍历前确定),该角在边外则整个瓦片在外,免去逐像素测试;横移 8 像素只需加 8a8a。
  2. 瓦片按之字形或空间填充曲线遍历以提高一致性,可多级(如先 16×16 再 4×4)。

相比扫描线顺序,分块遍历访问像素更一致,纹理(尤其配合 mipmap 时纹理缓存复用)、深度缓冲、颜色缓冲的局部性都更好;大三角形按扫描线遍历时,行首用过的纹素到行尾早已被逐出缓存。也因此纹理和缓冲本身都按瓦片存储。

三角形设置计算三角形上的常量(边方程系数、插值相关常量等)。裁剪必须在设置之前(可能生成更多三角形):近平面裁剪总是需要(生成一或两个三角形),屏幕边缘则多用保护带裁剪——例如保护带为 x、y 各 ±16K 像素,与屏幕相交且完全位于保护带内的三角形无需真正裁剪,只处理可见瓦片;只有超出保护带又与屏幕相交的三角形才需要完整裁剪。

重心坐标与插值(23.1.1 节)

属性插值 a(u,v)=(1−u−v)a0+ua1+va2a(u,v)=(1-u-v)a_0+ua_1+va_2,u=A1/(A0+A1+A2)u=A_1/(A_0+A_1+A_2),v=A2/(A0+A1+A2)v=A_2/(A_0+A_1+A_2),AiA_i 为子三角形面积。由于 e2(p)=n2⋅(p−p0)=∥n2∥∥p−p0∥cos⁡α=bh=2A2e_2(\mathbf{p})=\mathbf{n}_2\cdot(\mathbf{p}-\mathbf{p}_0)=\lVert\mathbf{n}_2\rVert\lVert\mathbf{p}-\mathbf{p}_0\rVert\cos\alpha=bh=2A_2(∥n2∥\lVert\mathbf{n}_2\rVert 等于边长 bb,投影长度为子三角形高 hh),

(u,v)=(e1(x,y),  e2(x,y))e0(x,y)+e1(x,y)+e2(x,y)(u,v)=\frac{(e_1(x,y),\;e_2(x,y))}{e_0(x,y)+e_1(x,y)+e_2(x,y)}

三角形设置预先计算 1/(A0+A1+A2)1/(A_0+A_1+A_2) 免去逐像素除法,于是内部测试的副产品就给出了重心坐标。这对深度插值和正交投影是正确的,但透视下会出错(棋盘纹理会扭曲)。

透视校正:屏幕空间中可线性插值的是 a/wa/w 与 1/w1/w(ww 为变换后的第四分量),

a=线性插值的 a/w线性插值的 1/wa=\frac{\text{线性插值的 } a/w}{\text{线性插值的 } 1/w}

例:边两端 a0=4a_0=4、a1=6a_1=6,w0=1w_0=1、w1=3w_1=3。a/wa/w 两端为 4 和 2,中点 3;1/w1/w 两端为 1 和 1/3,中点 2/3;相除得中点 a=4.5a=4.5,而正交投影下是 5。

实际要插值多个属性,所以先算透视校正重心坐标。令 fi(x,y)=ei(x,y)/wif_i(x,y)=e_i(x,y)/w_i(设置阶段可预存 a0/w0a_0/w_0 等,或把三个函数都乘以 w0w1w2w_0w_1w_2),

(u~,v~)=(f1(x,y),  f2(x,y))f0(x,y)+f1(x,y)+f2(x,y)(\tilde u,\tilde v)=\frac{(f_1(x,y),\;f_2(x,y))}{f_0(x,y)+f_1(x,y)+f_2(x,y)}

每像素算一次即可用于所有属性;它们不再与子三角形面积成正比,分母也不是常数,所以必须逐像素除法。深度 z/wz/w 本身已经除过 ww,不应用此式,而是逐顶点算 zi/wiz_i/w_i 后用 (u,v)(u,v) 线性插值——这对深度缓冲压缩也有好处。

保守光栅化(23.1.2 节)

DirectX 11 起(OpenGL 通过扩展)支持:高估保守光栅化 (OCR)(外保守)访问所有与三角形重叠或在其内的像素;低估 (UCR)(内保守)只访问完全在三角形内的像素。可用把瓦片缩小到一个像素的分块遍历实现;无硬件支持时可用几何着色器或三角形扩张实现 OCR。用途包括图像空间碰撞检测、遮挡剔除、阴影计算、抗锯齿等。

flowchart LR
  A["裁剪: 近平面 + 保护带"] --> B["三角形设置: 边方程与插值常量"]
  B --> C["分层瓦片遍历: 瓦片角测试"]
  C --> D["逐采样内部测试 + 左上规则"]
  D --> E["重心坐标与透视校正插值"]
  E --> F["按 2x2 quad 派发像素着色"]

如上图,光栅化是几何与像素处理之间的桥梁。

GEA 的视角:重心坐标的面积求法与透视校正(卷 II 11.3.10.6–11.3.10.7、11.4.5.3、11.4.6.1)

面积法求重心坐标

  • 片元位置 pf\mathbf{p}_f 可写成三个顶点的加权平均 pf=λ1p1+λ2p2+λ3p3\mathbf{p}_f=\lambda_1\mathbf{p}_1+\lambda_2\mathbf{p}_2+\lambda_3\mathbf{p}_3,权重之和为 1;点在三角形内时三个权重都为正,在外时至少一个为负(卷 II 11.3.10.6)。
  • 重心坐标也叫面积坐标:从 pf\mathbf{p}_f 到三个顶点的向量把三角形分成三个子三角形,权重就是子三角形面积与总面积之比。用边向量 e12=p2−p1\mathbf{e}_{12}=\mathbf{p}_2-\mathbf{p}_1 等与 vif=pf−pi\mathbf{v}_{if}=\mathbf{p}_f-\mathbf{p}_i 的叉积求面积:

Atotal=12∣e12×e13∣,A1=12∣e23×v2f∣,A2=12∣e31×v3f∣,A3=12∣e12×v1f∣A_{total}=\tfrac12|\mathbf{e}_{12}\times\mathbf{e}_{13}|,\quad A_1=\tfrac12|\mathbf{e}_{23}\times\mathbf{v}_{2f}|,\quad A_2=\tfrac12|\mathbf{e}_{31}\times\mathbf{v}_{3f}|,\quad A_3=\tfrac12|\mathbf{e}_{12}\times\mathbf{v}_{1f}|

λi=Ai/Atotal,λ3=1−(λ1+λ2)\lambda_i=A_i/A_{total},\qquad \lambda_3=1-(\lambda_1+\lambda_2)

这与本页「边函数的值恰好是子三角形面积的两倍」是同一个事实。

  • 补充(GEA 公式的两个隐含条件):① 叉积取模得到的面积恒为非负,按上式算出的 λi\lambda_i 不可能为负,无法像书中所说用"有负值"判断点在外部;要用有符号面积(二维下即边函数,三维下取叉积与三角形法线 n\mathbf{n} 的点积)。② 透视校正公式中的 λi\lambda_i 必须是屏幕空间(投影后)的重心坐标;若像书中面积法那样直接用三维顶点和三维片元位置求重心坐标,得到的已经是透视正确的权重,再套 1/z1/z 公式反而会算错。

透视校正插值

  • 直接用 zf=λ1z1+λ2z2+λ3z3z_f=\lambda_1z_1+\lambda_2z_2+\lambda_3z_3 插值深度是错的,因为透视投影不保持距离(透视缩短)。GEA 不给推导,只给结论:对深度的倒数做线性插值再取倒数(卷 II 11.3.10.7):

1zf=λ11z1+λ21z2+λ31z3\frac{1}{z_f}=\lambda_1\frac{1}{z_1}+\lambda_2\frac{1}{z_2}+\lambda_3\frac{1}{z_3}

  • 其他属性同理,例如颜色 Cf=zf(λ1z1C1+λ2z2C2+λ3z3C3)\mathbf{C}_f=z_f\left(\frac{\lambda_1}{z_1}\mathbf{C}_1+\frac{\lambda_2}{z_2}\mathbf{C}_2+\frac{\lambda_3}{z_3}\mathbf{C}_3\right)。直观理解:先撤销透视除法,正常插值,再把透视除法做回去。本页 RTR4 部分用 1/w1/w 表述同一件事。
  • 以 quad 为单位光栅化的目的是让像素着色器对任意着色器变量(最常见的是纹理坐标)求屏幕空间差分 ddx/ddy,以选择 mip 级别;GEA 原文写成"相邻片元三维位置的一阶差分",范围说窄了(见 纹理过滤与 Mipmap)。

保守光栅化:书中前后说法不一

  • 卷 II 11.4.6.1 写道保守光栅化"从 DirectX 12 开始"可选。
  • 同书 11.4.5.3 的特性级别说明则写它在特性级别 11_1 引入、从 12_1 起成为必需(表 11.2 中 11_0 及以下为"不支持")。
  • 两处说法并不完全一致(一处按 API 版本、一处按特性级别),这里照录。保守光栅化的原理见本页「保守光栅化」一节。
  1. realtime_rendering_4ed.pdf 第 23 章 Graphics Hardware
  2. Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
  3. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 11 章 11.3.10.6–11.3.10.7、11.4.5.3、11.4.6.1 节

相关页面

图形学光栅化Rasterization光栅化(也叫扫描转换)把屏幕空间里带深度和着色数据的顶点,转换成三角形覆盖的像素(片元),交给像素处理阶段。它是几何处理和像素处理之间的同步点:三个顶点在这里组成三角形。图形学GPU 计算调度与占用率GPU Compute Scheduling & OccupancyGPU 用大量统一的 ALU 以 SIMD/SIMT 方式锁步执行着色程序,ALU 与寄存器文件、L1、本地数据存储、纹理单元等组成多处理器 (MP),由硬件 warp 调度器零开销地切换 warp 以隐藏纹理和内存访问的长延迟。能同时驻留的 warp 数受寄存器等资源限制,用占用率衡量;占用率低会妨碍延迟隐藏,但过高也可能抖动缓存。本页还讨论像素按 quad 调度和 GPU→CPU 回读延迟。图形学硬件深度剔除与纹理单元Hardware Depth Culling & Texture UnitsGPU 的深度管线先在瓦片级用 HiZ 单元做 zmax/zmin 粗剔除,再逐采样做 early-z 深度测试以免执行被遮挡片元的像素着色器,必要时退回到着色后的 late depth test;深度缓冲同样有瓦片压缩与快速清除。纹理单元以固定功能硬件完成寻址、过滤、钳位与解压(比软件快可达 40 倍),按 quad 有限差分求导数以选择 LOD,并依靠瓦片化/Morton 交织存储和纹理缓存提高局部性。游戏数学光线求交测试Ray Intersection Tests光线求交用于拾取、光线追踪、地面高度查询等。本页给出四类经典测试:光线-球(代数解与带早期拒绝的几何优化解)、光线-盒(平板法与光线斜率法)、光线-三角形(Möller-Trumbore,直接从顶点求重心坐标与距离)、光线-多边形(先求平面交点,再投影到二维做交叉数测试)。共同思路是先做便宜的拒绝测试、推迟除法与开方、必要时降维。游戏数学包围体重叠测试Bounding Volume Overlap Tests碰撞检测先测试包围体是否重叠,只有重叠时才逐图元测试;最底层则需要三角形之间、三角形与盒之间的精确测试。本页整理 RTR4 第 22.10~22.13 节:平面与 AABB/OBB(只测两个角的"投影半径"法)、三角形-三角形(Guigue-Devillers 行列式法)、三角形-AABB(13 轴 SAT)、球-球、球-盒(Arvo 及其向量化版本)、AABB-AABB、k-DOP-k-DOP 与 OBB-OBB(15 轴 SAT)。更复杂的 BV(OBB、k-DOP)空体积更少、更紧,代价是测试更贵。图形学像素着色器Pixel Shader像素着色器(OpenGL 里叫片元着色器)对光栅化产生的每个片元运行,用插值后的顶点数据算出颜色,也可以输出不透明度、修改深度或直接丢弃片元。多渲染目标让它一次输出多组数据,催生了延迟着色。它通常只能写自己所在的像素,但能通过 2×2 quad 拿到相邻像素的导数。图形学坐标空间Coordinate Spaces顶点从模型到屏幕要依次经过模型空间、世界空间、观察空间、裁剪空间、标准化设备坐标 (NDC)、窗口坐标。每一步变换都有明确目的,大部分可以用 4×4 矩阵表示并相乘合并。综合帧时间预算与渲染性能Frame Budget & Rendering Performance本页把 wiki 中分散在 RTR4 第 18~23 章的性能知识串成一条线:一帧的时间预算由最慢的管线阶段决定,先用毫秒帧时间度量并定位瓶颈,再按瓶颈所在位置——CPU/驱动、几何、像素、带宽/内存——选用对应手段;最后从 GPU 硬件机制(占用率、HiZ/early-z、压缩、分块架构)理解这些手段为何有效。各结论的细节与出处见所链接的概念页。游戏开发 · UE5Nanite 虚拟化几何体Nanite Virtualized GeometryNanite 是 UE5 的虚拟化几何体 (Virtualized Geometry) 系统:导入时把网格切成分层的三角形簇 (Cluster),渲染时按相机视角逐簇选择细节层次并无缝拼接,按需从磁盘流送,并在绕过传统绘制调用 (Draw Call) 的独立通道中渲染,使帧预算不再受多边形数、绘制调用和网格内存约束。图形学纹理过滤与 MipmapTexture Filtering & Mipmap纹理在屏幕上被放大时需要插值,被缩小时一个像素覆盖许多纹素,必须预先滤波才能避免混叠。放大用最近邻、双线性或双三次;缩小的主流方案是 mipmap + 三线性插值,斜视角下用各向异性过滤减少过度模糊。注意:过滤纹理值只在输入与着色结果线性相关时等价于过滤最终颜色。

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原