直线裁剪算法
前面我们讲了如何填充和变换二维图形,这篇文章来讲解如何对直线段进行裁剪——判断哪些部分在窗口内、哪些在窗口外,只保留该画的部分。
直线裁剪算法
基本概念
裁剪(Clipping) 是图形管线中的关键一步:给定一个矩形窗口和一条直线段,求出该线段位于窗口内的部分。
直线裁剪算法主要有两种分类方式:
按思想分类:
- 区域编码类(Region Code)——给点打标签,快速判断位置关系
- 参数方程类(Parametric)——用线段参数方程统一处理,使用最广泛
- 中点递归/分治类(Subdivision)——反复取中点逼近边界
按窗口形状分类:
- 矩形窗口裁剪(最常见)
- 任意凸多边形窗口裁剪
- 非凸窗口裁剪
下面按思想分类来介绍。
区域编码类
Cohen–Sutherland 算法
基本思想:把窗口周围划分成 9 个区域,每个点用一个 4 位二进制编码标记其位置。通过对线段两端点的编码做位运算,快速判断”全在内部”“全在外部”还是”需要裁剪”。
区域编码规则:
四位编码从左到右依次表示:上、下、右、左。某一位为 1 表示该点在对应边界之外。
1 | |
编码规则:
- 第1位(上):
- 第2位(下):
- 第3位(右):
- 第4位(左):
对线段两端点 code0 和
code1,有三种情况:
1. 完全接受
1 | |
两端点都在窗口内(都为 0000),整条线段无需裁剪,直接保留。
2. 完全拒绝
1 | |
两端点的编码在同一位上都是 1,说明线段完全在窗口的某一侧(比如两个端点都在左边),直接丢弃。
3. 部分可见
不满足上面两种情况时,线段跨越多条边界,需要逐条边界求交、更新端点、重新编码,直到满足情况 1 或 2。
裁剪过程(部分可见时):
1 | |
当两端点某一位同时为 1 时(
code0 & code1 != 0),线段完全在该边界之外,可以直接丢弃,省去了与该边界求交的计算。这是 Cohen-Sutherland 相比盲目求交算法的核心优势。
具体例子
设窗口:
线段
第一步:编码
: → 左位为 1; 在范围内 → 下/上为 0; 没超过 → 右为 0。编码:0001(左)。 : → 右位为 1;其余为 0。编码:0010(右)。
第二步:判断状态
code0 | code1 = 0001 | 0010 = 0011 ≠ 0→ 不是完全接受code0 & code1 = 0001 & 0010 = 0000 == 0→ 不是完全拒绝- 结论:部分可见,需要裁剪。
第三步:逐边裁剪
替换
现在两端点编码:
替换
此时 code0 | code1 = 0000 →
完全接受,算法结束。
裁剪后的线段:
算法特点:
优点: - 思想直观,位运算极快 - 大量线段完全在内部或外部时效率很高
缺点: - 部分可见时可能逐边求多次交点 - 对于跨越多条边界的线段,每步都要重新编码
Nicholl–Lee–Nicholl(NLN)算法对 Cohen-Sutherland 做了优化,通过额外预判减少了求交次数,但实现复杂度显著提高,实际应用较少,在这里我们不做介绍。
参数方程类
这类算法把线段表示为参数方程,通过求”合法的参数区间”来裁剪,避免了逐点编码的反复试探。
Liang–Barsky 算法
这是唯一一个以中国人(梁友栋 & Barsky)命名的计算机图形学经典算法。
基本思想:不是反复求线段和窗口边界的交点再判断,而是一次性算出线段上”能被窗口看见”的参数区间
参数方程
线段
推导不等式
对四条窗口边界分别代入约束:
- 左边界:
→ → - 右边界:
→ → - 下边界:
→ → - 上边界:
→ →
统一写成
- 左:
, - 右:
, - 下:
, - 上:
,
分析
对于每条边
:除以负数要变号 → 。 必须大于等于这个值才能满足约束,即点从这条边进入窗口。更新进入时间: :直接除以正数 → 。 超出这个值就不满足约束了,即点从这条边离开窗口。更新离开时间: :线段与该边界平行。此时 消失,需要单独判断 : - 若
,则 不成立 → 线段在窗口外 → 直接丢弃 - 若
,不等式恒成立 → 此边界不构成限制 → 忽略
- 若
算法步骤
1 | |
注意:
可能 < 0(线段起点已深入窗口内部), 可能 > 1(终点在进入窗口前),这都不影响判断,只要 就有可见段。
具体例子(用同一个场景对比)
窗口:
线段
初始:
逐边处理:
逐边处理:
处理左边界:
处理右边界:
处理下边界:
处理上边界:
最终区间:
计算裁剪后端点:
裁剪后线段:
Cyrus–Beck 算法
基本思想:Cyrus-Beck 是 Liang-Barsky
的推广。Liang-Barsky 用四条边的
整体思路与 Liang-Barsky
一致:遍历每条边,根据该边是”进入边”还是”离开边”,更新
推导
参数方程(记方向向量
窗口边的表示:对于凸多边形的第
:该边上的任意一点(通常取一个端点) :该边的内法向量,指向窗口内部
核心条件:点
将
展开:
解出交点参数
进入 / 离开分类:系数
(进入边):除以正数不变号,得 ,给出下界——线段从此边进入窗口 (离开边):除以负数变号,得 ,给出上界——线段从此边离开窗口 (平行): 从不等式中消失。此时检查 : - 若
:线段整体在窗口外侧 → 直接丢弃 - 若
:该边不构成限制 → 忽略
- 若
算法步骤
1 | |
具体例子
用与前两个算法相同的场景,但这次把窗口视为一个四边形的凸多边形(逆时针排列)。
窗口顶点:
线段:
初始:
逐边处理:
逐边处理:
处理 e0(
处理 e1(
处理 e2(
处理 e3(
各列的详细计算说明(以 e0 为例):
(逆时针旋转 90°,指向窗口内部即上方) → 进入边 (实际上这条边不缩小 )
最终区间:
裁剪后线段:
与 Liang-Barsky 的关系
Cyrus-Beck 用在矩形窗口上时,四条边的
对应关系:Cyrus-Beck 的
Cyrus-Beck 的通用性在于:只需更换
中点递归/分治类
这一类算法不用参数方程,而是反复取线段中点来逼近窗口边界。
基本思路:
- 对线段两端点编码(同 Cohen-Sutherland)
- 若完全接受 → 保留;若完全拒绝 → 丢弃
- 否则取线段中点
,将线段一分为二 - 对两段子线段递归执行步骤 1-3
- 当线段长度小于某个阈值时停止
这种方法不需求交点,只需编码判断和中点计算(整数运算),在早期硬件上实现方便。但由于递归调用多,性能不如 Liang-Barsky,现代已较少使用。
总结对比
| Cohen-Sutherland | Liang-Barsky | Cyrus-Beck | |
|---|---|---|---|
| 分类 | 区域编码 | 参数方程 | 参数方程 |
| 窗口形状 | 矩形 | 矩形 | 任意凸多边形 |
| 核心操作 | 编码 + 位运算 + 逐边求交 | 求 |
法向量 + 点积 |
| 求交次数 | 可能多次 | 每条边最多算一次 | 每条边最多算一次 |
| 效率 | 一般(大量全在内部/外部时很快) | 高 | 中(法向量计算稍多) |
| 实现难度 | 简单 | 中等 | 中等偏难 |
实际使用中,如果只需要矩形窗口裁剪,Liang-Barsky 是首选——一次遍历四条边,没有重复编码的开销,实现简洁高效。
这次的插图来自画师Chigu
图片地址:https://www.pixiv.net/artworks/69618131