这篇文章来介绍直线扫描转换算法
DDA数值微分线段算法 算法简介 数值微分法即DDA法(Digital Differential Analyzer),是一种基于微分方程来生成直线的方法。在计算机图形学中,并没有线段的概念,而是一个个像素点组成了线段。
DDA法生成线段的步骤一般如下:
有了起始点($x_1,y_1$)和终点($x_n,y_n$);
$$\Delta x =|x_n-x_1|, \Delta y=|y_n-y_1|$$
比较$\Delta x$和$\Delta y$的大小; steps=$\Delta x$和$\Delta y$中较大者;
$$step_x=\frac{\Delta x}{steps},step_y=\frac{\Delta y}{steps}$$
算法实现 DDA算法实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include <cmath> #include <iostream> #include <GL/glut.h> const int WIDTH = 640 ;const int HEIGHT = 480 ;void drawDDALine (int x1, int y1, int x2, int y2) { float x = x1; float y = y1; int dx = x2 - x1; int dy = y2 - y1; int steps = std::abs (dx) > std::abs (dy) ? std::abs (dx) : std::abs (dy); float xIncrement = (float )dx / steps; float yIncrement = (float )dy / steps; glBegin (GL_POINTS); glVertex2i ((int )round (x), (int )round (y)); for (int k = 0 ; k < steps; k++) { x += xIncrement; y += yIncrement; std::cout << (int )round (x) << ", " << (int )round (y)<<"\n" ; glVertex2i ((int )round (x), (int )round (y)); } glEnd (); } void display () { drawDDALine (0 , 0 , 50 , 20 ); glFlush (); } void init () { glClearColor (1.0f , 1.0f , 1.0f , 1.0f ); glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluOrtho2D (0.0 , WIDTH, 0.0 , HEIGHT); } int main (int argc, char ** argv) { glutInit (&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutInitWindowSize (WIDTH, HEIGHT); glutInitWindowPosition (100 , 100 ); glutCreateWindow ("DDA算法" ); glutDisplayFunc (display); init (); glutMainLoop (); return 0 ; }
中点画线算法 算法简介 只考虑当直线的斜率$|k|< 1$时的情况,假设现在有一条直线$(x_1,y_1,x_2,y_2)$,那么第一个点一定是$(x_1,y_1)$无疑,下一个点的$x$坐标为$x_1+1$,$y$坐标要么为$y1$要么为$y1+1$。关键在于每次取下一个点时,是取前一个的$y1$呢,还是$y1+1$,这时一定是取直线上点最靠近的那个了,而判断取哪个点就用到了中点,我们将中点代入直线中 $d=F(x_1+1,y_1+0.5)=a \cdot (x_1+1)+b \cdot (y_1+0.5)+c$。
如果直线$d>=0$,则取下边的点也就是$(x_1+1,y_1)$。
如果直线$d<0$,则取上边的点也就是$(x_1+1,y_1+1)$。
它的实际过程就是这样每次根据前边的点判断下一个点在哪,然后进行打亮,但这样每次判断的时候都得代入直线方程计算太麻烦了,我们将这俩种情况分别代入直线方程中可以找出规律:
当直线$d>=0$时,经过化解得$d_1=d+a$;
当直线$d<0$时,经过化解得$d_2=d+a+b$;
初始值$d_0=a+0.5b$。
也就是说每次的增量要么为$a$,要么为$a+b$,那么这样判断的时候就简单多了,因为我们每次只是判断它的正负。所以给等式同时乘2,将其中浮点数0.5化为整数,这样硬件操作时无疑更快了。
算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <GL/freeglut.h> #include <iostream> #include <cmath> void drawLineMidpoint (int x0, int y0, int x1, int y1) { int a = y0 - y1; int b = x1 - x0; int d = 2 * a + b; int d1 = 2 * a; int d2 = 2 * (a + b); int x = x0, y = y0; glBegin (GL_POINTS); glVertex2i (x, y); while (x < x1) { if (d < 0 ) { x++; y++; d += d2; } else { x++; d += d1; } glVertex2i (x, y); } glEnd (); } void display () { glClear (GL_COLOR_BUFFER_BIT); glColor3f (1.0 , 1.0 , 1.0 ); drawLineMidpoint (50 , 50 , 450 , 300 ); glFlush (); } void init () { glClearColor (0.0 , 0.0 , 0.0 , 1.0 ); glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluOrtho2D (0 , 500 , 0 , 500 ); } int main (int argc, char ** argv) { glutInit (&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutInitWindowSize (500 , 500 ); glutInitWindowPosition (100 , 100 ); glutCreateWindow ("Midpoint Line Algorithm - FreeGLUT" ); init (); glutDisplayFunc (display); glutMainLoop (); return 0 ; }
Bresenham算法 算法简介 假设我们需要由$(x_0, y_0)$这一点,绘画一直线至右下角的另一点$(x_1, y_1)$,x,y分别代表其水平及垂直坐标,并且$x_1 - x_0 > y_1 - y_0$。在此我们使用电脑系统常用的坐标系,即$x$坐标值沿$x$轴向右增长,$y$坐标值沿$y$轴向下增长。
因此x及y之值分别向右及向下增加,而两点之水平距离为$x_{1}-x_{0}$且垂直距离为$y_{1}-y_{0}$。由此得之,该线的斜率必定介乎于$0$至$1$之间。而此算法之目的,就是找出在$x_{0}$与$x_{1}$之间,第$x$行相对应的第$y$列,从而得出一像素点,使得该像素点的位置最接近原本的线。
对于由$(x_0, y_0)$及$(x_1, y_1)$两点所组成之直线,公式如下: $$y-y_{0}={\frac {y_{1}-y_{0}}{x_{1}-x_{0}}}(x-x_{0})$$ 因此,对于每一点的x,其y的值是 $${\frac {y_{1}-y_{0}}{x_{1}-x_{0}}}(x-x_{0})+y_{0}$$ 因为$x$及$y$皆为整数,但并非每一点$x$所对应的$y$皆为整数,故此没有必要去计算每一点x所对应之$y$值。反之由于此线之斜率介乎于$1$至$0$之间,故此我们只需要找出当$x$到达那一个数值时,会使$y$上升$1$,若$x$尚未到此值,则$y$不变。至于如何找出相关的$x$值,则需依靠斜率。斜率之计算方法为$m=(y_{1}-y_{0})/(x_{1}-x_{0})$。由于此值不变,故可于运算前预先计算,减少运算次数。
要实行此算法,我们需计算每一像素点与该线之间的误差。于上述例子中,误差应为每一点$x$中,其相对的像素点之$y$值与该线实际之$y$值的差距。每当$y$的值增加$1$,误差的值就会增加$m$。每当误差的值超出$0.5$,线就会比较靠近下一个映像点,因此$y$的值便会加$1$,且误差减$1$。
算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <GL/freeglut.h> #include <iostream> #include <cmath> void drawLineBresenham (int x0, int y0, int x1, int y1) { int dx = abs (x1 - x0); int dy = abs (y1 - y0); int sx = (x0 < x1) ? 1 : -1 ; int sy = (y0 < y1) ? 1 : -1 ; int err = dx - dy; glBegin (GL_POINTS); while (true ) { glVertex2i (x0, y0); if (x0 == x1 && y0 == y1) break ; int e2 = 2 * err; if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; } } glEnd (); } void display () { glClear (GL_COLOR_BUFFER_BIT); glColor3f (0.0f , 0.8f , 0.4f ); drawLineBresenham (50 , 50 , 450 , 400 ); drawLineBresenham (50 , 400 , 450 , 50 ); drawLineBresenham (250 , 50 , 250 , 450 ); drawLineBresenham (50 , 250 , 450 , 250 ); glFlush (); } void init () { glClearColor (0.1f , 0.1f , 0.1f , 1.0f ); glMatrixMode (GL_PROJECTION); glLoadIdentity (); gluOrtho2D (0 , 500 , 0 , 500 ); } int main (int argc, char ** argv) { glutInit (&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutInitWindowSize (600 , 600 ); glutCreateWindow ("Bresenham Line Algorithm" ); init (); glutDisplayFunc (display); glutMainLoop (); return 0 ; }