直线段扫描转换算法

数学里的直线上的点是无穷多的,但是光栅显示器的像素点是有限的,所以只能使用有限的像素去尽可能逼近直线

00.png

为了在光栅显示器上用这些离散的像素点去逼近直线,需要知道这些像素点的 $x$, $y$ 坐标

求法:

1. 求出过点 $P_{0}$, $P_{1}$ 的直线段的方程:

2. 求 $y$ 的值

假设 $x$ 已知,即从 $x$ 的起点 $x_{0}$ 开始,沿着 $x$ 方向前进一个像素(步长 = 1),可以计算出相应的 $y$ 值

因为像素的坐标是整数,所以 $y$ 值还需要进行取整处理

取整规律:

直线是最基本的图形,一个动画或者真实感图形往往需要调用成千上万次画线程序,因此直线算法的好坏与效率将直接影响图形的质量和显示速度

算法优化:把算法中的乘法消去可以减少计算量,从而提高算法效率

三个直线绘制常用算法:

  1. 数值微分法(DDA)
  2. 中点画线法
  3. Bresenham 算法

1. 数值微分法

引进了增量思想,算法直观、容易实现

原理解析:

01.png

在上图中点 $\left(x_{i},\;y_{i}\right)$, $\left(x_{i+1},\;y_{i+1}\right)$ 可以表示为:

因为存在假设:$x$ 已知,且每次前进步长为 1,所以:$x_{i+1} = x_{i} + 1$

则:

即:当前的 $y$ 值等于前一步的 $y$ 值 + 斜率 $k$

这里的斜率 $k$ 也就是式子 $y_{i+1} = y_{i} + k$ 的增量

这样就把原来一个乘法和加法变成了一个加法

eg. 用 DDA 扫描转换连接两点 $P_{0} \left(0,\;0\right)$ 和 $P_{1} \left(5,\;3\right)$ 的直线段

02.png

1. 计算 $k$

2. 计算像素点坐标

$x$ $y$ $int \left(y + 0.5\right)$
$0$ $0$ $0$
$1$ $0+0.6$ $1$
$2$ $0.6+0.6$ $1$
$3$ $1.2+0.6$ $2$
$4$ $1.8+0.6$ $2$
$5$ $2.4+0.6$ $3$

03.png

注意:

当 $|k| > 1$ 时

使用上述计算方式可能不能画出直线,因为任意两点之间通过 DDA 算法计算直线,当 $|k| > 1$ 时,结果只有三个点,如果两点之间距离比较远,就会导致光栅点太稀疏,从而不能描绘直线

2. 中点画线法

对 DDA 的改进方案:

1. 提高算法效率

因为在计算机中,加法运算是最快的,加法运算里整数加法又是最快的

一般情况下,$y$ 和 $k$ 都是小数,而且每一步运算都要对 $y$ 进行四舍五入后取整,所以可以通过把浮点运算变成整数运算来改进

2. 改变直线方程类型

中点画线法:利用直线的一般式方程

这样一来,对于直线上的点:$F \left(x,\;y\right) = 0$

对于直线上方的点:$F \left(x,\;y\right) > 0$

对于直线下方的点:$F \left(x,\;y\right) < 0$

原理解析:

每一次在最大位移方向上走一步,而另一个方向走不走取决于中点误差项的判断

04.png

假设:$0 \leq |k| \leq 1$,所以每次在 $x$ 方向上 $+1$,$y$ 方向上的变化情况需要通过判断 $P_{u}$ 到 $P_{d}$ 的中点来决定

如图,此时 ideal line 与直线 $P_{u}P_{d}$ 有一个交点 $Q$,直线 $P_{u}P_{d}$ 有一个中点:$M \left(x_{i}+1,\;y_{i}+0.5\right)$

判断:

  • 当 $Q$ 在 $M$ 上方时,$P_{u}$ 距离直线更近,则 $P_{u}$ 为下一像素点
  • 当 $Q$ 在 $M$ 下方时,$P_{d}$ 距离直线更近,则 $P_{d}$ 为下一像素点
  • 若 $Q$ 和 $M$ 是同一个点,则 $P_{u}$ 或 $P_{d}$ 都可以是下一像素点

如何判断 $Q$ 在 $M$ 上方还是下方?

1. 把 $M$ 代入理想直线方程:

2. 判断

  • 当 $d < 0$ 时:$Q$ 在 $M$ 上方,下一像素点为 $P_{u}$
  • 当 $d > 0$ 时:$Q$ 在 $M$ 下方,下一像素点为 $P_{d}$
  • 当 $d = 0$ 时:$Q$ 等于 $M$,下一像素点为 $P_{u}$ 或 $P_{d}$ 都可以

算法:

算法计算量:为了求得 $d$ 值,需要 $4$ 个乘法,$2$ 个加法

对中点画线法的改进:因为 $d$ 是 $x$, $y$ 的线性函数,所以可以采用增量计算

推导过程:

05.png

代入中点坐标求得 $d_{0}$:

假设 $d < 0$

则第一个取的像素点 $P_{0}$ 为 $P_{u} \left(x_{i} + 1,\;y_{i} + 1\right)$

再往下计算一个像素点 $P_{1}$ 的位置:

则 $P_{1}$ 可能是点 $P_{a} \left(x_{i}+2,\;y_{i}+1\right)$ 或者 $P_{b} \left(x_{i}+2,\;y_{i}+2\right)$

则直线 $P_{a}P_{b}$ 的中点为 $M_{1} \left(x_{i}+2,\;y_{i}+1.5\right)$

所以:

假设 $d \geq 0$

则第一个取的像素点 $P_{0}’$ 为 $P_{d} \left(x_{i} + 1,\;y_{i}\right)$

再往下计算一个像素点 $P_{1}’$ 的位置:

则 $P_{1}’$ 可能是点 $P_{a} \left(x_{i}+2,\;y_{i}+1\right)$ 或者 $P_{c} \left(x_{i}+2,\;y_{i}\right)$

则直线 $P_{a}P_{c}$ 的中点为 $M_{1}’ \left(x_{i}+2,\;y_{i}+0.5\right)$

所以:

$$d_{1} = F \left(x_{{m1}'},\;y_{{m1}'}\right)$$

计算 $d_{0}$:

直线的第一个像素点坐标为:$P\left(x_{0},\;y_{0}\right)$

因为 $P \left(x_{0},\;y_{0}\right)$ 在直线上,所以满足 $Ax_{0} + By_{0} + C = 0$

所以:

中点画线法算法:

至此,中点画线法算法与 DDA 的算法效率一样好

注意:

一般情况下,$A$、$B$ 都是整数,而 $d_{0}$ 的计算中存在浮点数,浮点数的运算更复杂

但是 $d_{0}$ 的作用只是与 $0$ 比较大小,所以完全可以用 $2d_{0}$ 代替 $d_{0}$,以此可以避免浮点数运算

此时中点画线法算法中只包含整数运算,所以更优于 DDA 算法

3. Bresenham 算法

Bresenham 拥有更优的效率和更广泛的适用范围

基本思想:

06.png

通过各行、各列像素中心构造一组虚拟网格线,按照直线起点到终点的顺序,计算直线与各垂直网格线的交点,然后根据误差项(交点距离像素点的距离)的符号确定该列像素中与此交点最近的像素

假设每次 $x+1$,$y$ 的递增(减)量为 $0$ 或 $1$,它取决于实际直线与光栅网格点的距离,这个距离的最大误差是 $0.5$

  • 如果 $d < 0.5$,$y$ 的递增(减)量为 $0$
  • 如果 $d > 0.5$,$y$ 的递增(减)量为 $1$
  • 如果 $d = 0.5$,$y$ 的递增(减)量为 $0$ 或 $1$ 中任意一个

误差项 $d$ 的初值 $d_{0} = 0$

初始之后的 $d = d + k$

一旦 $d \geq 1$,就将其减 $1$,以保证 $d$ 的相对性,且始终在 $0$、$1$ 之间

计算下一个象素点的原理:

算法效率优化:

1. 令 $e = d - 0.5$

将值的大小替换成正负号

  • $e_{初} = -0.5$
  • 每当 $x + 1$,则 $e = e + k$
  • 如果 $e > 0$,则 $e = e - 1$

2. 令 $2e\Delta x = e$

因为 $k = \frac{dy}{dx}$、$dx = \Delta x$

  • $e_{初} = -\Delta x$
  • 每当 $x + 1$,则 $e = e + 2\Delta y$
  • 如果 $e > 0$,则 $e = e - 2\Delta x$

至此,算法全部改进为整数加法,且不受直线方程类型的限制

算法步骤:

  1. 输入直线的两端点 $P_{0}\left(x_{0},\;y_{0}\right)$ 和 $P_{1}\left(x_{1},\;y_{1}\right)$
  2. 计算初始值 $\Delta x$、$\Delta y$、$e = -\Delta x$、$x = x_{0}$、$y = y_{0}$
  3. 绘制点 $\left(x,\;y\right)$
  4. 将 $e$ 替换为 $e + 2\Delta y$,判断 $e$ 的符号
    • 若 $e > 0$,则将 $\left(x,\;y\right)$ 替换为 $\left(x+1,\;y+1\right)$,同时将 $e$ 替换为 $e - 2\Delta y$
    • 否则,将 $\left(x,\;y\right)$ 替换为 $\left(x+1,\;y\right)$
  5. 当直线没有画完时,重复步骤 3 和 4,否则结束

Bresenham 算法很像 DDA 算法,都是加斜率,但 DDA 算法是每次求出一个新的 $y$ 以后取整来画,而 Bresenham 算法是判符号来决定上下两个点,所以该算法集中了 DDA 和中点算法的优点,而且应用范围更广泛

4. 结

以上是基础原理,实现代码🕳

---------------- The End ----------------