【计算机图形学】小白谈计算机图形学(一)画线篇之DDA算法,中点画线法,Bresenham画线法及相关改进详解

489 阅读7分钟

【计算机图形学】画线篇之DDA算法,中点画线法,Bresenham画线法及相关改进详解

引言

大家好,众所周知,计算机的图像显示是由一个一个像素组成,正如分子组成了世界,当像素合理分布,同样可以还原很多真实世界的场景。

如何画线

基本思想

已知过端点 p 0 ( x 0 , y 0 ) p_0(x_0,y_0) p0​(x0​,y0​)和 p 1 ( x 1 , y 1 ) p_1(x_1,y_1) p1​(x1​,y1​)的直线:
y = k x + b y=kx+b y=kx+b
直观的做法是把每个 x x x的值代入直线方程求出相应的 y y y值。取像素点 ( x , i n t ( y ) ) (x, int(y)) (x,int(y))作为当前点坐标。光栅直线算法属于图形学最底层的算法,因此要精益求精。

数值微分法(DDA算法)

数值微分基本思路

增量算法变乘法为加法,这里利用斜截式,首先 y i + 1 = y i + k Δ x y_{i+1}= y_i+ k\Delta x yi+1​=yi​+kΔx,当 Δ x = 1 \Delta x=1 Δx=1时:
y i + 1 = y i + k y_{i+1}= y_i+ k yi+1​=yi​+k

数值微分改进

  • 此处,我们做细节处理,将像素格划分为上半和下半, 用 i n t ( y + 0.5 ) int(y+0.5) int(y+0.5)判断是上方还是下方涂色。
    在这里插入图片描述
  • 同时该算法只能画 ∣ k ∣ ≤ 1 |k| \leq1 ∣k∣≤1,超过则会出现离散的点,失真。可以采用:
    { y i + 1 = y i + k ( ︱ k ︱ < 1 , Δ x = 1 ) x i + 1 = x i + 1 k ( ︱ k ︱ > 1 , Δ y = 1 ) \left\{ \begin{aligned} y_{i+1} = y_i+k( ︱k︱<1,\Delta x =1)\\ \\ x_{i+1} = x_i+\frac{1}{k}( ︱k︱>1,\Delta y =1) \end{aligned} \right. ⎩⎪⎪⎪⎨⎪⎪⎪⎧​yi+1​=yi​+k(︱k︱<1,Δx=1)xi+1​=xi​+k1​(︱k︱>1,Δy=1)​
  • 优点:简单直观,迭代的算法
    缺点:浮点运算,每步都需四舍五入取整。

中点画线法

中点画线引言

利用后一中点与前一中点的函数关系,采用了直线的一般式方程
F ( x , y ) = y − y 1 − y 0 x 1 − x 0 x − b F(x,y)=y-\frac{y_1-y_0}{x_1-x_0}x-b F(x,y)=y−x1​−x0​y1​−y0​​x−b
认为 Δ x > 0 \Delta x>0 Δx>0,即:
F ( x , y ) = ( Δ x ) y − ( Δ y ) x − ( Δ x ) b F(x,y)=(\Delta x)y-(\Delta y)x-(\Delta x)b F(x,y)=(Δx)y−(Δy)x−(Δx)b
直线方程将平面分为三个区域:
{ F ( x , y ) = 0 ( 直 线 上 的 点 ) F ( x , y ) > 0 ( 直 线 上 方 的 点 ) F ( x , y ) < 0 ( 直 线 下 方 的 点 ) \left\{ \begin{aligned} F(x, y) = 0(直线上的点)\\ F(x, y) > 0(直线上方的点) \\ F(x, y) < 0(直线下方的点) \end{aligned} \right. ⎩⎪⎨⎪⎧​F(x,y)=0(直线上的点)F(x,y)>0(直线上方的点)F(x,y)<0(直线下方的点)​
在这里插入图片描述
当 M M M在 Q Q Q的下方,则说明 P u P_u Pu​离直线近,应为下一个像素点;当 M M M在 Q Q Q的上方,应取 P d P_d Pd​ 为下一点。但是…每一个像素的计算量是4个加法,两个乘法。比DDA算法的计算量大多了,毫无可取之处!

中点画线改进

根据上一次计算的 d d d值判断接下来 d d d值的变化

  • 若 d < 0 d<0 d<0时,则取右上方像素 p u ( x i + 1 , y i + 1 ) p_u(x_i+1, y_i+1) pu​(xi​+1,yi​+1)。判断再下一
    像素,则要计算:
    d n e w = F ( x i + 2 , y i + 1.5 ) = a ( x i + 1 ) + b ( y i + 0.5 ) + c + a = F ( x i + 1 , y i + 0.5 ) + a + b = d o l d + a + b \begin{aligned} d_{new}&=F(x_i+2,y_i+1.5)\\ &=a(x_i+1)+b(y_i+0.5)+c+a\\ &=F(x_i+1,y_i+0.5)+a+b\\ &=d_{old}+a+b \end{aligned} dnew​​=F(xi​+2,yi​+1.5)=a(xi​+1)+b(yi​+0.5)+c+a=F(xi​+1,yi​+0.5)+a+b=dold​+a+b​
  • 若 d ≥ 0 d \geq 0 d≥0情况,则取正右方像素 ( x i + 1 , y i ) (x_i+1, y_i) (xi​+1,yi​), 判断下一个像素位置要计算:
    d n e w = F ( x i + 2 , y i + 0.5 ) = a ( x i + 1 ) + b ( y i + 0.5 ) + c + a = F ( x i + 1 , y i + 0.5 ) + a = d o l d + a \begin{aligned} d_{new}&=F(x_i+2,y_i+0.5)\\ &=a(x_i+1)+b(y_i+0.5)+c+a\\ &=F(x_i+1,y_i+0.5)+a\\ &=d_{old}+a \end{aligned} dnew​​=F(xi​+2,yi​+0.5)=a(xi​+1)+b(yi​+0.5)+c+a=F(xi​+1,yi​+0.5)+a=dold​+a​
  • 迭代需要初始值 d 0 d_0 d0​
    d 0 = F ( x 0 + 1 , y 0 + 0.5 ) = a ( x 0 + 1 ) + b ( y 0 + 0.5 ) + c = a + 0.5 b \begin{aligned} d_0&=F(x_0+1,y_0+0.5) \&=a(x_0+1)+b(y_0+0.5)+c \&=a+0.5b \end{aligned} d0​​=F(x0​+1,y0​+0.5)=a(x0​+1)+b(y0​+0.5)+c=a+0.5b​
  • 由于我们只用 d d d的符号,所以使用 2 d 2d 2d摆脱小数运算更新后的公式为:
    { d 0 = 2 a + b d n e w = d o l d + 2 a + 2 b ( d < 0 ) d n e w = d o l d + 2 a ( d ≥ 0 ) \left\{ \begin{aligned} &d_0= 2a+b\\ &d_{new}=d_{old}+2a+2b(d<0) \\ &d_{new}=d_{old}+2a(d \geq 0) \end{aligned} \right. ⎩⎪⎨⎪⎧​​d0​=2a+bdnew​=dold​+2a+2b(d<0)dnew​=dold​+2a(d≥0)​

Bresenham画线法

Bresenham基本思路

采用增量计算,检查误差项的符号,就可以确定该列的所求像素。

  • 直线的起始点在像素中心,所以误差项d的初值
    { d 0 = 0 d n e w = d o l d + k x i + 1 = x i + 1 ( 右 移 一 格 ) y i + 1 = { y i + 1 , d = d − 1 ( d ≥ 0.5 ) ( 上 移 一 格 ) y i ( d < 0.5 ) ( 保 持 不 动 ) \left\{ \begin{aligned} &d_0=0\\ &d_{new}=d_{old}+k\\ &x_{i+1}= x_i+1(右移一格)\\ &y_{i+1}= \left\{ \begin{aligned} &y_i+1,d=d-1&(d\geq0.5)(上移一格)\\ \\ &y_{i}&(d < 0.5)(保持不动) \end{aligned} \right. \\ \end{aligned} \right. ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧​​d0​=0dnew​=dold​+kxi+1​=xi​+1(右移一格)yi+1​=⎩⎪⎨⎪⎧​​yi​+1,d=d−1yi​​(d≥0.5)(上移一格)(d<0.5)(保持不动)​​
    在这里插入图片描述

Bresenham画线改进

  • 令 e = d − 0.5 e=d-0.5 e=d−0.5
    { e 0 = − 0.5 e n e w = e o l d + k x i + 1 = x i + 1 ( 右 移 一 格 ) y i + 1 = { y i + 1 , e = e − 1 ( e ≥ 0 ) ( 上 移 一 格 ) y i ( e < 0 ) ( 保 持 不 动 ) \left\{ \begin{aligned} &e_0=-0.5\\ &e_{new}=e_{old}+k\\ &x_{i+1}= x_i+1(右移一格)\\ &y_{i+1}= \left\{ \begin{aligned} &y_i+1,e=e-1&(e\geq0)(上移一格)\\ \\ &y_{i}&(e < 0)(保持不动) \end{aligned} \right. \\ \end{aligned} \right. ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧​​e0​=−0.5enew​=eold​+kxi+1​=xi​+1(右移一格)yi+1​=⎩⎪⎨⎪⎧​​yi​+1,e=e−1yi​​(e≥0)(上移一格)(e<0)(保持不动)​​
  • e ∗ 2 e*2 e∗2将 e 0 e_0 e0​变为整数,由于 p 0 ( x 0 , y 0 ) , p 1 ( x 1 , y 1 ) p_0(x_0,y_0),p_1(x_1,y_1) p0​(x0​,y0​),p1​(x1​,y1​)皆为整数,故 d ∗ Δ x d* \Delta x d∗Δx即为斜率乘 Δ x \Delta x Δx即为 Δ y \Delta y Δy为整数,故第二步改进令:
    e = e ∗ 2 ∗ Δ x e=e*2*\Delta x e=e∗2∗Δx
    判断 e e e的正负,更新后的公式为:
    { e = − Δ x ( 初 始 值 ) e n e w = e o l d + 2 Δ y ( 初 始 值 ) x i + 1 = x i + 1 ( 右 移 一 格 ) y i + 1 = { y i + 1 , e = e − 2 Δ x ( e ≥ 0 ) ( 上 移 一 格 ) y i ( e < 0 ) ( 保 持 不 动 ) \left\{ \begin{aligned} &e= -\Delta x(初始值)\\ &e_{new}= e_{old}+2\Delta y(初始值)\\ &x_{i+1}= x_{i}+1(右移一格)\\ &y_{i+1}= \left\{ \begin{aligned} &y_i+1,e=e-2\Delta x&(e\geq0)(上移一格)\\ \\ &y_{i}&(e < 0)(保持不动) \end{aligned} \right. \\ \end{aligned} \right. ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧​​e=−Δx(初始值)enew​=eold​+2Δy(初始值)xi+1​=xi​+1(右移一格)yi+1​=⎩⎪⎨⎪⎧​​yi​+1,e=e−2Δxyi​​(e≥0)(上移一格)(e<0)(保持不动)​​

小结

避免浮点运算,进行整数算法判断像素点位置,就必须判断符号,如中点画线法和Bresenhanm算法,判断大小不能搞成整数加法。
wuli放几张做好的图,大家康康c语言可以画图呀!
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

超链接

如果你还想了解其他内容:
小白谈计算机图形学(一)如何画线
小白谈计算机图形学(二)如何画圆
小白谈计算机图形学(三)二维图形裁剪
小白谈计算机图形学(四)二维三维图形变换—1