【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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM