計算機圖形學基礎 : 基本圖形生成演算法之直線的掃描轉換
阿新 • • 發佈:2019-02-14
學習了三種常用的直線掃描轉換演算法 :數值微分法(DDA)、中點畫線法和Bresenham畫線演算法.
注 : 本文中的程式都是假定斜率在0~1之間,其他斜率類似,做相應的簡單處理就好。
數值微分法(DDA, Digital Differential Analyzer)
直線掃描轉換最簡單的方法是先算出直線的斜率: k = △y / △x △x = x1 - x0,△y = y1 - y0, (x0, y0)和(x1, y1)是線段的兩個端點座標。然後從線的起點開始,確定最近逼近於直線的y座標。假設端點座標都為整數,讓x從起點開始到終點,步增為1,計算對應的y座標,y = kx + B,並取畫素(x, round(y))。用這種方法可行、直觀,但是效率很差,因為每步運算都需要一個浮點乘法和一個舍入運算yi+1 = kxi+1 + B = k(xi + △x) + B = kxi + B + k△x = yi + k△x 因此,當△x = 1時,有yi+1 = yi + k,每當x步增1,y遞增k。 DDA演算法程式為:
int round(float a) { return int(a + 0.5); } void DDAline(int x0, int y0, int x1, int y1, int color) { int x; float dx, dy, k, y; dx = x1 - x0; dy = y1 - y0; k = dx / dy; y = y0; for (x = x0; x <= x1; ++x) { DrawPixel(x, round(y), color); y = y + k; } }
在這個演算法中,y和k必須用浮點數表示,每一步運算又需要將y進行舍入取整,這使得該演算法不利於硬體的實現。
中點畫線法
中點畫線法的基本原理是:直線在x方向增加一個單位,則在y方向上的增量只能是0和1之間。如圖,假設x座標為xp的的各畫素點中,與直線最近者已確定,為(xp, yp),用實心小圓表示;那麼下一個與直線最近的畫素只能是正右方P1(xp + 1, yp) 或者是右上方的點P2(xp + 1, yp + 1)兩者之一。 M(xp + 1, yp + 0.5) 表示P1和P2的中點,Q是理想直線與垂直線x = xp + 1的交點,如果M點在Q點下方,很明顯P2離直線更新一些,如果M在Q的上方,則P1離直線更新一些,我們取離直線最近的那個畫素點,如果M和Q重合,我們約定取正右方的那個點。 假設起點和終點為 (x0, y0)和(x1, y1),則直線方程為: F(x, y) = ax + by + c, 其中a = y0 - y1, b = x1 - x0,c = x0y1 - x1y0
d的增量為 a + b. d的初始值,第一個畫素應取左端點(x0, y0),相應的判別式為: d0 =F(x0 + 1, y0 + 0.5) = a(x0 + 1) + b(yp + 0.5) + c = ax0 + by0 + c + a + 0.5b = F(x0, y0) + a + 0.5b 由於(x0, y0)在直線上,所以F(x0, y0) = 0,因此,d的初始值為a + 0.5b。 d的初始值包含小數,因此可以用2d來代替d,寫出僅包含整數運算的演算法:
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;
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);
}
}
Bresenham畫線演算法
該演算法檢查一個誤差項的符號就可以確定該列的畫素。 如圖,假設x列的畫素已確定,其行下標為y,那麼下一個畫素的列座標比為x + 1,而行座標要麼不變,要麼遞增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)。 令 e = d - 0.5,當e >= 0時 , 下一個畫素值y遞增1,e < 0時y不遞增,e初始值為-0.5. 演算法如下:
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;
k = dx / dy;
e = -0.5; // 因為d的初始值為0
x = x0;
y = y0;
for (int i = 0; i <= dx; ++i)
{
DrawPixel(x, y, color);
x += 1;
e = e + k;
if (e >= 0) // 虛擬碼,實際比較浮點數不這樣進行比較
{
y += 1;
e = e - 1;
}
}
}
寫完會發現,該演算法會用到小數和除法,為了利於硬體實現,可以改用整數以避免除法,演算法中只用到誤差項的符號,因此可以作如下替換: e' = 2 * e * dx; 整數Bresenham演算法為:
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;
}
}
}
以上所有程式均是考慮斜率在0~1之間的時候~ 初學者,歡迎各位大牛指出錯誤!