DDA畫線算法
一.算法介紹
DDA是一種增量算法,也就是說通過對前一個點在X和Y軸方向上加上一個增量,從而得到一個新點得坐標。這個算法要求先算出直線的斜率,然后從起點開始,確定最佳逼近於直線
的y坐標。假設起點的坐標為整數,直線方程為y=kx+b,k的取值在0到1之間,x每遞增1,y相應地遞增k。因為像素的坐標是整數,所以y需要進行取整處理。對新坐標行四舍五入得到
整型y值,確定一個要渲染得像素點。從而畫出一條直線。
二.算法解析:
當 | k | <= 1時:
已知過端點 P0(x0, y0),P1(x1, y1) 的直線段 L(P0, P1);直線斜率為 k = (y1 - y0) / (x1 - x0)。於是 yi+1 = kxi+1 + b。
1.x每遞增1,y相應地遞增k,每次計算點的坐標,我只需要x+1,y+k就行了,避免了乘除,而只有加減。
↓
2.通過增量得到下一個點后,可知此坐標肯定會與實際像素點有偏差(實際的直線並不是和像素點完全重合的)。所以采取四舍五入得方式來平衡差值,但是
C/C++中取整是直接舍去小數值,所以采取加上0.5再取整,即int(y+0.5)。從而達到一個四舍五入的目的。
當 | k | > 1時:
若仍然使 k = (y1 - y0) / (x1 - x0),則會導致斜率大於1。從而在x遞增1的時候y可能會出現遞增大於1的數,使得像素點不相鄰,直線不連續,出現斷點的情況。
此時應令k = (x1 - x0) / (y1 - y0),然后執行y+1,x+k 。最后再對x四舍五入int(x+0.5)。
三、代碼
// 數值微分法,偽代碼。通過此代碼可理解算法的基本思路
當 | k | <= 1時:
當 | k | > 1時:
綜上可得:
中點畫線算法
圖1 圖2
引用這2個圖是因為實際像素為圖1,圖2中為了方便算法的描繪,采用像素中心來代替整個像素點。所以不要單純由圖2產生錯誤理解,要聯系實際的物理分布。
一、算法介紹
這里的中點指的是直線與虛擬網格交點Q的上下像素點的連線中點M。將M與Q點進行比較,若Q與M重合或者在M之下,則渲染P1點。else,渲染上方
的P2點。從而渲染出一條直線。
二、算法解析
1.設坐標的單位為1,斜率k<1,起點和終點為 (x0, y0)和(x1, y1),則F(x, y) = ax + by + c, 其中a = y0 - y1, b = x1 - x0,c = x0y1 - x1y0
(a,b,c是如何得到的?)
解:
已知直線上兩點求直線的一般式方程
2.算法流程
當 | k | <= 1時
對於直線上的點,F(x, y) = 0;直線上方的點,F(x, y) > 0;直線下方的點,F(x, y) < 0. 因此,判斷M在Q的上方還是下方,只需將M點帶入方程計算判別式:
解釋:為何要用2d代替d?這是為了來擺脫浮點運算,只進行整數的加法,相比於DDA算法進一步提高了效率。而d只是用來比較正負,所以乘以倍數不會改變符號。
當 | k | > 1時
方法與DDA同理
三、代碼
以下代碼為|k|<=1時,只包含整數運算的方法:
void MidPointLine(int x0, int y0, int x1, int y1, int color)
{
int a, b, delta1, delta2, d, x, y;
a = y0 - y1;
b = x1 - x0;
d = 2 * a + b; //求d的初值
delta1 = 2 * a;
delta2 = 2 * (a + b); //兩種不同情況下的增量
x = x0;
y = y0;
DrawPixel(x, y, color);
while (x < x1)
{
if (d < 0)
{
++x; //取右上方的點
++y;
d += delta2;
}
else
{
++x; //取正右方的點
d += delta1;
}
DrawPixel(x, y, color);
}
二、算法解析
y
1.如圖,假設每次x + 1,而y要么不變,要么遞增1,是否遞增1取決於誤差項d的值(如上圖所示)。因為直線起始點在像素中心,所以誤差項d的初始值為0,x下標每增加1,d的值相應遞增直線的斜率值,即d = d + k. 一旦d >= 1,將它的值減去1,使得d的值總保持在0到1之間。
當d >= 0.5時,直線與x + 1列垂直網格線交點最接近於當前像素的右上方像素點(x + 1, y + 1),d < 0.5時則為正右方像素(x + 1, y)。
2.為了將算法也改進為整數加減,所以要進行變動。
改進1:大小判定簡化到正負判定
令 e = d - 0.5,當e >= 0時 ,下一個像素值y遞增1,e < 0時y不遞增。此時e初始值為-0.5。每走一步e=e+k,當渲染后需再次對e進行判定,以保證其相對區間。
當e>0時,e=e-1。(此處-1操作會產生誤解,可以轉換對象來看,e只是為了轉換d的判別形式,所以可以直接看d。e>0與d>0.5一致,下一次取點d>1,此時d-1所以e-1)
↓
改進2:去除0.5這個浮點數運算
兩邊同乘2得:2e=2d-1,2e=2e+2k
↓
改進3:去除求斜率k=dy/dx的除法運算
兩邊再同乘dx得:2dx*e = 2dx*d - dx,2dx*e = 2dx*e + 2dx*k = 2dx*e + 2dy。令e=2dx*e得,e=e-dx,e=e+2dy。同理,在e>0時執行的e=e-1變為e=e-2dx。
↓
結果:初始值d=0,化簡上式可得 ①e0 = -dx ②e = e + 2dy ③e=e-2dx
最后總結一下具體步驟:
1.輸入直線的兩個端點P0(x0,y0)和P1(x1,y1)
2.計算初始值Δx、Δy、e=-Δx、x=x0,y=y0
3.繪制點(x,y)
4.e更新為e+2Δy,判斷e的符號,若e>0,則(x,y)更新為(x+1,y+1),同時將e更新為e-2Δx;else 將(x,y) 更新為(x+1,y)
5.當直線沒有畫完時,重復3和4,否則結束。
三、代碼
//偽代碼如下
void Bresenham(int x0, int y0, int x1, int y1, int color)
{
int x, y, dx, dy;
float k, e;
dx = x1 - x0;
dy = y1 - y0;
e = -dx
x = x0;
y = y0;
for (int i = 0; i <= dx; ++i)
{
DrawPixel(x, y, color);
x += 1;
e = e + 2 * dy;
if (e >= 0) // 偽代碼,實際比較浮點數不這樣進行比較
{
y += 1;
e = e - 2 * dx;
}
}
}
三種算法的對比
一、DDA
采用的是浮點數運算,不易於硬件實現。
二、中點畫線算法
只有整數運算,不含乘除,可用硬件實現。相對於DDA提高了效率
三、Bresenham算法
1.在渲染點的循環中沒有計算直線的斜率,不用做除法。
2.不用浮點數,只做整數加法和乘2運算,乘2運算可用硬件位移實現。
3.應用范圍不拘束於直線的方程形式
4.Bresenham算法”其實“比較像數值微分法,也是增量算法,但是相對來說更利於硬件實現。
四、總結
中點畫線算法比DDA效率更高,Bresenham算法比中點畫線算法的應用范圍更廣。