【OpenGL学习】 四种绘制直线的算法

我是用MFC框架进行测试的,由于本人也没有专门系统学习MFC框架,代码若有不足之处,请指出。

 

一,先来一个最简单的DDA算法

DDA算法全称为数值微分法,基于微分方程来绘制直线。

①推导微分方程如下:

 

②,dM时间步长的倒数的详解:

可以看到

当|k|<=1时

dx=1或者-1,此时的x为计长方向

当|k|>1时

dy=1或者-1,此时的y为计长方向

绘制时需要用dM来控制绘制的点数

③绘制像素的问题:

 

为了“方便”管理算法,我为不同的绘制函数新建了一个类了。。。(其实可以写到一个类里面。。。。)

④代码实现:

MyDDA.cpp

 1 #include "stdafx.h"
 2 #include "MyDDA.h"
 3 
 4 
 5 MyDDA::MyDDA()
 6 {
 7 }
 8 
 9 
10 MyDDA::~MyDDA()
11 {
12 }
13 
14 void MyDDA::SetDc(CDC * dc)
15 {
16     this->pdc = dc;
17 }
18 
19 void MyDDA::Moveline(CPoint start, CPoint end)
20 {
21     int x1, x2;
22     int y1, y2;
23     x1 = start.x;
24     x2 = end.x;
25     y1 = start.y;
26     y2 = end.y;
27     float dm = 0;
28     if (abs(x2 - x1) >= abs(y2 - y1))
29         dm = abs(x2 - x1);
30     else
31         dm = abs(y2 - y1);
32     float dx = (float)(x2 - x1) / dm;
33     float dy = (float)(y2 - y1) / dm;
34     float x = x1 + 0.5;
35     float y = y1 + 0.5;
36     int i = 0;
37     while (i<dm) {
38         this->pdc->SetPixel((int)x, (int)y, RGB(0, 0, 0));
39         x += dx;
40         y += dy;
41         i += 1;
42     }
43 
44 }

 

总结:

其实这个算法还算是挺简单的,就是要确定是X还算Y为计长方向,需要注意的是循环过程中计长方向自增1,另一个方向按照当直线在计长方向自增1时,直线在轴上的投影的大小自增就可以了。

 

 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

二,逐点比较法

逐点比较法是在画线的过程中,每画一点就与理想直线比较,以决定下一点的走向,以一步步逼近的方法点亮最接近直线的一组像素。

 ①绘制前先将直线平移

使y坐标较小的点位于坐标原点(绘制的时候再平移回去,跟几何变换一个道理)

如图:

②推导过程:

先来谈一下直线的偏移值怎么表示

 

可以看到:

如果d是正值说明当前绘制点在绘制直线的上方。

反之,在下方。

③由于每次计算都需要计算两次乘法,计算工作量大,所以可以利用递推公式来,借助当前点的偏移值,获取下一个点的偏移值。

递推公式推导过程如下图所示:

④终点判断:

 

 ⑤整理下我们在编程时需要的参数:

⑥实现代码:

注释部分的代码比较容易理解一点,但是太长了,注释下面的代码是简化的版本,两者的作用是一样的。

PtoP.cpp

  1 void PtoP::Moveline(CPoint start, CPoint end)
  2 {
  3     int xA, yA;
  4     if (start.y > end.y) {
  5         xA = start.x - end.x;
  6         yA = start.y - end.y;
  7     }
  8     else
  9     {
 10         xA = end.x - start.x;
 11         yA = end.y - start.y;
 12     }
 13 
 14     int n = abs(xA) + abs(yA);
 15 
 16     int x = 0, y = 0, F = 0;
 17     /*
 18     if (xA > 0)//1
 19     {
 20         for (int i = 0; i < n; i++)
 21         {
 22             if (F >= 0)
 23             {
 24                 x += 1;
 25                 F -= yA;
 26                 if(start.y>end.y)
 27                 this->pdc->SetPixel(x+end.x,y+end.y, RGB(0, 0, 0));
 28                 else
 29                 this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0));
 30             }
 31             else
 32             {
 33                 y += 1;
 34                 F += xA;
 35                 if (start.y > end.y)
 36                     this->pdc->SetPixel(x + end.x, y + end.y, RGB(0, 0, 0));
 37                 else
 38                     this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0));
 39             }
 40         }
 41 
 42     }
 43     else//2
 44     {
 45         for (int i = 0; i < n; i++)
 46         {
 47             if (F >= 0)
 48             {
 49                 y += 1;
 50                 F += xA;
 51                 if (start.y > end.y)
 52                     this->pdc->SetPixel(x + end.x, y + end.y, RGB(0, 0, 0));
 53                 else
 54                     this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0));
 55             }
 56             else
 57             {
 58                 x-= 1;
 59                 F += yA;
 60                 if (start.y > end.y)
 61                     this->pdc->SetPixel(x + end.x, y + end.y, RGB(0, 0, 0));
 62                 else
 63                     this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0));
 64             }
 65 
 66         }
 67 
 68     }
 69     */
 70     for (int i = 0; i < n; i++) {
 71         if (xA > 0) {
 72             if (F >= 0)
 73             {
 74                 x++;
 75                 F -= yA;
 76             }
 77             else
 78             {
 79                 y++;
 80                 F += xA;
 81             }
 82         }
 83         else {
 84             if (F >= 0)
 85             {
 86                 y++;
 87                 F += xA;
 88             }
 89             else
 90             {
 91                 x--;
 92                 F += yA;
 93             }
 94         }
 95         if (start.y > end.y)
 96             this->pdc->SetPixel(x + end.x, y + end.y, RGB(0, 0, 0));
 97         else
 98             this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0));
 99     }
100 }

 

⑦总结:

逐点比较法绘制最重要的是递推公式的推导,以及避免原偏移值公式的无理运算。

 

 

 

 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

三,Bresenham画线法

①引入

Bresenham画线算法是应用最广泛的直线生成算法,它采用加减以及乘2运算(即移位运算)来实现。

首先先从直线斜率为0<=k<=1的八分之一象限来开始讨论。

如图:

 

 ②如何判别选取点S还是点T

 

③迭代公式

 

④起点的判别值

 

 ⑤其他情况的判别

 

 

⑥整理所需变量

 

⑦代码实现

Bresenham.cpp

 1 #include "stdafx.h"
 2 #include "Bresenham.h"
 3 
 4 
 5 Bresenham::Bresenham()
 6 {
 7 }
 8 
 9 
10 Bresenham::~Bresenham()
11 {
12 }
13 
14 void Bresenham::SetDc(CDC * dc)
15 {
16     this->pdc = dc;
17 }
18 
19 void Bresenham::Moveline(CPoint start, CPoint end)
20 {
21     int x1=start.x, y1=start.y;
22     int x2=end.x, y2=end.y;
23 
24     this->pdc->SetPixel(start.x,start.y,RGB(0,0,0));
25     int dx, dy;
26     dx = abs(x2-x1);
27     dy = abs(y2-y1);
28     int flag=0;
29     if (dx == 0 && dy == 0)
30         return;
31     if (dy > dx)
32     {
33         flag = 1;
34         swap_value(x1,y1);
35         swap_value(x2,y2);
36         swap_value(dx,dy);
37     }
38     int tx = (x2 - x1) > 0 ? 1 : -1;
39     int ty = (y2 - y1) > 0 ? 1 : -1;
40     int curx = x1 + 1;
41     int cury = y1;
42     int dS = 2 * dy;
43     int dT = 2 * (dy-dx);
44     int d =dS-dx;
45     while (curx != x2)
46     {
47         if (d >= 0) {
48             d += dT;
49             cury += ty;
50         }
51         else
52         {
53             d += dS;
54         }
55         if (flag)
56             this->pdc->SetPixel(cury,curx,RGB(0,0,255));
57         else
58             this->pdc->SetPixel(curx, cury, RGB(0, 0, 255));
59         curx+=tx;
60     }
61 }
62 
63 void Bresenham::swap_value(int & a, int & b)
64 {
65     /*
66     a ^= b;
67     b ^= a;
68     a ^= b;
69     */
70     int temp = a;
71     a = b;
72     b = temp;
73 
74 }

 

总结:Bresenham算法关键点还是在于迭代公式的推导,以及如何选择下一个点,判断x轴和y轴的步长是自增还是自减

 

 

 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

四,中点画线法

 中点画线法的思路跟Bresenham算法的思路极为相似,在学习完之后,我就觉得两者的算法不同之处就是取点的判断标准

①构造判别式

 

②递推公式推导

③其他斜率情形

 

 

 ④整理变量

⑤代码实现

 1 #include "stdafx.h"
 2 #include "MidpointLine.h"
 3 
 4 
 5 MidpointLine::MidpointLine()
 6 {
 7 }
 8 
 9 
10 MidpointLine::~MidpointLine()
11 {
12 }
13 
14 void MidpointLine::SetDc(CDC * dc)
15 {
16     this->pdc = dc;
17 }
18 
19 void MidpointLine::Moveline(CPoint start, CPoint end)
20 {
21     int x0 = start.x, x1 = end.x, y0 = start.y, y1 = end.y;
22     int a, b, d2, x, y, flag = 0;
23     if (abs(x1 - x0) < abs(y1 - y0))
24     {
25         swap_value(x0,y0);
26         swap_value(x1, y1);
27         flag = 1;
28     }
29     if (x0 > x1) {//保证x0<x1,方便判别斜率
30         swap_value(x0, x1);
31         swap_value(y0, y1);
32     }
33     a = y0 - y1;
34     b = x1 - x0;
35     d2 = 2*a + b;//摆脱小数点,提高效率
36     if (y0 < y1) {//k>0
37         x = x0; y = y0;
38         this->pdc->SetPixel(x,y,RGB(0,0,0));
39         while (x < x1)
40         {
41             if (d2 < 0)
42             {
43                 x++;
44                 y++;
45                 d2 =d2+ 2*a + 2*b;
46             }
47             else {
48                 x++;
49                 d2 =d2+ 2 * a;
50             }
51 
52             if(flag)//|k|>1
53                 this->pdc->SetPixel(y, x, RGB(0, 0, 0));
54             else
55                 this->pdc->SetPixel(x, y, RGB(0, 0, 0));
56         }
57     }
58     else {//k<0
59         x = x1;
60         y = y1;
61         this->pdc->SetPixel(x, y, RGB(0, 0, 0));
62         while (x >x0)
63         {
64             if (d2 < 0)
65             {
66                 x--;
67                 y++;
68                 d2 = d2-2 * a + 2 * b;
69             }
70             else {
71                 x--;
72                 d2 =d2- 2 * a;
73             }
74 
75             if (flag)//|k|>1
76                 this->pdc->SetPixel(y, x, RGB(0, 0, 0));
77             else
78                 this->pdc->SetPixel(x, y, RGB(0, 0, 0));
79         }
80     }
81 }
82 
83 void MidpointLine::swap_value(int & a, int & b)
84 {
85     /*
86     a ^= b;
87     b ^= a;
88     a ^= b;
89     */
90     int temp = a;
91     a = b;
92     b = temp;
93 }

 

总结:

判断的准则不同也导致函数的写法跟之前Bresenham函数有很大的差别,最明显的就是中点判断法需要保证绘制直线的走向,以方便判断直线的斜率,而Bresenham算法则不需要这一点,它只需要知道tx,ty就能知道直线的走向,对于起点,终点在哪里。

并没有太大的约束。

 

最后来一张四种算法同时绘制直线的合照吧。。。。

最左边是DDA,依次是逐点比较法,Bresenham,中点画线法。

 

该工程的github链接:https://github.com/Thousandyearsofwar/DrawLine

 

posted @ 2019-08-04 15:58 凶恶的真实 阅读(...) 评论(...) 编辑 收藏