Deep Read

多边形裁剪算法

  在光栅化之前,落在可视范围之外的几何体必须被裁掉,否则会浪费光栅化和着色的算力,甚至因为顶点跑到相机背后(w ≤ 0)而算出错误的屏幕坐标。裁剪就发生在这个环节:在裁剪空间里,用视锥体的各个平面去切割图元。这里以二维的窗口裁剪为例,讲解经典的 Sutherland-Hodgman 算法。

目标分析

对一个 Mesh 的一次 Draw 调用,其中的每个三角形相对可视范围会有三种情况,裁剪要分别处理:

  1. 三角形完全在可视范围内:所有顶点都保留,原样通过。
  2. 三角形与可视范围边界相交:去掉范围外的顶点,并在边界上产生新的交点顶点,重新组成一个(或多个)位于范围内的多边形。
  3. 三角形完全在可视范围外:丢弃所有顶点,整个图元被剔除。

  第二种情况是裁剪的难点:一个三角形被边界切开后,剩下的部分可能是四边形甚至更多边,需要生成新的顶点来"封住"切口。Sutherland-Hodgman 算法正是用来系统地处理这件事的。

Sutherland-Hodgman 算法

Sutherland-Hodgman 算法也叫逐边裁剪法,核心思路是"分割处理、逐边裁剪":不一次性用整个窗口去裁多边形,而是一次只用窗口的一条边去裁剪整个多边形,把这条边的结果作为下一条边的输入,循环处理完所有边。这样就把复杂的多边形-窗口裁剪,拆解成了简单的多边形-单直线裁剪。

算法思想(二维)

  • 用窗口的一条边裁剪整个多边形,对窗口的每条边循环一次。
  • 每一轮,准备一个空的输出点数组 DST,输入多边形是 SRC
  • 每一轮,从 0 号顶点开始遍历 SRC 的所有顶点:当前顶点作为 S 点,它后面的那个顶点作为 P 点(首尾相接,最后一个点的 P 是 0 号点),依次测试每条边 S→P 相对当前裁剪线的位置,按下面的规则把结果输出到 DST
  • 一轮结束后令 SRC = DST,进入下一条裁剪边。全部裁剪边处理完,SRC 就是最终结果。

逐边测试的四种情况

对每条有向边 S→P,根据 S、P 各自在裁剪线的内侧还是外侧,分四种情况处理(下图中竖线为裁剪线,右为内侧、左为外侧,I 为边与裁剪线的交点):

  • 情况 1:S 外、P 外——整条边都在外侧,无输出。
  • 情况 2:S 内、P 外——边从内侧走向外侧,输出交点 I(P 被裁掉,但要在边界上留下切口点)。
  • 情况 3:S 外、P 内——边从外侧走向内侧,输出交点 IP 两个点(先补上进入边界的交点,再保留内侧的 P)。
  • 情况 4:S 内、P 内——整条边都在内侧,输出 P

四种情况可以归纳成一个简单准则:只要 P 在内侧就输出 P;只要 S、P 一内一外(边穿过了裁剪线)就额外输出交点 I。 每次只判断 P 是否输出,是为了避免相邻边把共享顶点重复输出。

按这个规则对每条裁剪边跑一遍,多边形就被逐步收敛到窗口内部;产生的新交点 I 自然地把被切开的边界"缝合"起来,最终得到一个完整、闭合、完全位于可视范围内的多边形。

这个二维逐边裁剪的思想可以直接推广到三维:GPU 硬件在裁剪空间中,用视锥体的六个平面(-w ≤ x ≤ w-w ≤ y ≤ w0 ≤ z ≤ w)依次对图元做同样的逐面裁剪。区别只是把二维的"内/外侧"判断换成四维齐次坐标下点到平面的符号判断,交点 I 则通过对 S、P 的属性(位置、UV、颜色等)做线性插值得到——这也是为什么裁剪必须在透视除法(见 【空间变换】屏幕映射(Screen Mapping)【空间变换】屏幕映射(Screen Mapping)经过 [[【空间变换】投影变换]],顶点已经在裁剪空间里了。屏幕映射是这条管线的最后一站,负责把裁剪空间的顶点落到屏幕上具体的像素位置。它包含两步: - 透视除法(Perspective Division) - 视口变换(Viewport Transform) 透视除法 image.png 上图是顶点数据的完整变换流程。局部空间的顶点,依次经过模型矩阵 Model、观察矩阵 View、投影矩阵 Projection,来到裁剪空间,此时满足 $-w_{clip} \leq x, y, z \leq w_{clip}$。接着做透视除法——每个分量都除以 $w$ 分量——就得到了标准设备坐标(NDC),它所在的空间也叫标准视体(Canonical View Volume,CVV)。 为什么这一步能实现近大远小? 关键在 $w$。回顾 [[【空间变换】投影变换]] 的推导,投影矩阵刻意把观察空间的 $z$ 坐标塞进了裁剪空间的 $w$ 分量。于是透视除法(除以 $w$,等于除以 $z$)就让离相机越远、$z$ 越大的顶点,坐标被压缩得越狠——这正是透视效果的来源。这也回扣了 [[【空)之前、于四维齐次空间中完成,才能正确处理跨越 w = 0 的情况。裁剪在整条硬件管线中的位置见 渲染流水线完全解析渲染流水线完全解析diagram.svg 完整的渲染流水线是一个高度并行、高度优化的过程。 这里按 数据流动 的顺序拆解:CPU 应用准备 → 驱动与命令提交 → GPU 前端几何处理 → GPU 后端像素处理 → 显示。   应用准备(CPU 端)     这一阶段完全在 CPU 上运行,是渲染的"备菜"阶段:把网格、纹理从慢速硬盘读进内存(RAM),解码成 GPU 能理解的原始数据结构(顶点数组、像素位图)。整个过程是串行的,受限于 I/O 和 CPU 解码速度。   数据从磁盘到 RAM 要走一条完整链路:应用程序(Unity/UE)向操作系统发出文件读取请求(如 ReadFile API),内核的文件系统驱动定位硬盘上的文件(如 Tank.fbx、Tank_Diffuse.png),通过磁盘控制器读取;数据先被读入系统文件缓存(RAM),再复制到应用进程的内存空间。 进入内存后还要解析(Parsing)成引擎可用的结构: 网格**:CPU 上的解析库(如 Assimp 或引擎自定义代码)读取 .fbx 的二进制内容,解码成顶点数组(float3 位置