1. 数值微分法
已知过端点
,
的直线段
;直线斜率
。画线的过程为:从
的左端点
开始,向右端点步进,步长为1(像素),按
计算相应的
坐标,并取像素点
作为当前坐标。但这样做,计算每一个点需要做一个乘法、一个加法。设步长为
,有
,于是
当
时,有
。即
每递增1,
递增
(即直线的斜率)。这样,计算就由一个乘法和加法减少为一个加法。
- 算法程序:DDA画线算法程序
void DDALine(int x0, int y0, int x1, int y1, int color)
{
int x;
float dx, dy, y, k;
dx = x1 - x0, dy = y1 - y0;
k = dy/dx, y = y0;
for(x = x0; x < x1; x++,y+=k)
{
drawpiexl(x, int(y + 0.5), color);
}
}
注意: 用int(y + 0.5)取整的目的是为取离真正交单近的像素网格点作为光栅化的点。
应当注意的是,上述算法仅适用于
的情形。在这种情况下,
每增加1,
最多增加1。当
时,必须把
地位互换,
每增加1,
相应增加
。本节的其它算法也是如此。在这个算法中,
与
必须用浮点数表示,而且每一步都要对
进行四舍五入后取整,这使得该算法不利于硬件的实现。
2.中点画线法
通过观察可以发现,画直线段的过程中,当前像素点
,下一个像素点有两种选择点
或
。若
为
与
的中点,
为理想直线与
垂线的交点。当
在
的下方时,
应为下一个像素点;当
在
的上方时,应取
为下一点。这就是中点画线法的基本原理(可见下图)。对直线段
,采用方程
表示,其中
。于是有下述点与
的关系:
因此,欲判断
在
上方还是下方,只要把
代入
,并判断它的符号即可。构造如下判别式:
当
,
在
(
点)下方,取
为下一像素;当
,
在
(
点)上方,取
为下一像素;当
,选
或
均可,约定取
为下一个像素。
用式(1.2)计算符号时,需要4个加法和2个乘法。事实上,
是
的线性函数,因此可采用增量计算,提高运算效率。方法如下
- (1)若 ,则取正右方像素 。判断下一个像素的位置时,应计算 ,增量为 。
- (2)若
,则取正右方像素
。判断下一个像素的位置时,应计算
,增量为
。
设从点 开始画线, 的初值 。因 ,则 。由于使用的只是 的符号,而 的增量都是整数,只是初始值包含小数。因此可以用 代替 来摆脱点运算,写出仅包含整数运算的算法。 - 算法程序:中点画线算法程序
void MidpointLine(int x0, int y0, int x1, int y1, int color)
{
int a, b, d1, d2, d, x, y;
a = y0 - y1, b = x1 - x0, d = 2*a + b;
d1 = 2*a, d2 = 2*(a + b);
x = x0, y = y0;
drawpixel(x,y,color);
while(x < x1)
{
if(d < 0)
x++, y++, d+=d2;
else
x++, d+=d2;
drawpixel(x,y,color);
}
}
3.Bresenham算法
Bresenham算法是在计算机图形学领域内使用最广泛的直线扫描转换算法。该方法类似于中点法,由误差项符号决定下一个像素取右边点还是右上角。
算法原理如下:过各行各列像素中心构造一组虚拟网络线,按直线从起点到终点的顺序计算直线与各垂直网络的交点,然后确定该列像素中与此交点最近的像素。该算法的巧妙在于采用增量计算,使得每一列,只要检查一个误差项的符号,就可以确定该列所求的像素。
如图所示,设直线方程为
,则有
,其中
为直线的斜率。假设
列的像素坐标已经确定为
,其行坐标
,那么下一个像素的列坐标为
,而行坐标要么为
要么为
。是否增1取决于图所示的误差项
的值。因为直线的起始点在像素中心,所以误差项
。
下标每增加1,
的值相应递增直线斜率值
,即
。当
时,直线与
列垂直网络的交点最接近于当前像素
的右上方像素
,该像素在
方向增加1,同时作为下一次计算的新基点,因此
值相应减1;而当
,更接近于右方像素
。为了方便计算,令
,
的初始值为-0.5,增量为k。当
,取当前像素
的右上方像素
,
减小1;而当
时,更接近于右方的像素
。
- Bresenham画线算法程序
void BresenhamLine(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 = dy/dx;
e = -0.5, x = x0, y = y0;
for(int i = 0; i < dx; i++)
{
drawpixel(x, y, color);
x = x + 1, e = e + k;
if(e >= 0)
y++, e = e-1;
}
}
上述Bresenham算法在计算直线斜率与误差项时用到了小数与除法,可以改用整数以避免除法。由于算法中只用到了误差项的符号,因此可以做如下替换 。
- 改进的Bresenham画线算法程序
void BresenhamLine(int x0, int y0, int x1, int y1, int color)
{
int x, y, dx, dy, 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 = x + 1, e = e + 2*dy;
if(e >= 0)
y++, e = e-2*dx;
}
}
4. 代码实现(VS2017 + OpenGL)
#include "pch.h"
#include <iostream>
#include <gl\glut.h>
#include <gl\GL.h>
#include <gl\GLU.h>
using namespace std;
void init(void)
{
glClearColor(1.0, 1.0, 1.0, 0.0); // Set display-window color to white.
glMatrixMode(GL_PROJECTION); // Set projection parameters.
gluOrtho2D(0.0, 200.0, 0.0, 150.0);
}
//窗口大小改变时调用的登记函数
void ChangeSize(GLsizei w, GLsizei h) {
if (h == 0)
h = 1;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(0.0f, 250.0f, 0.0f, 250.0f*h / w, 1.0, -1.0);
else
glOrtho(0.0f, 250.0f*w / h, 0.0f, 250.0f, 1.0, -1.0);
}
//DDA算法函数
void DDALine(int x0, int y0, int x1, int y1)
{
glColor3f(1.0, 0.0, 0.0); //设置像素点颜色为红色
glPointSize(2.0f); //设置像素点大小
int x;
float dx, dy, y, k;
dx = x1 - x0, dy = y1 - y0;
k = dy / dx, y = y0;
for (x = x0; x < x1; x++, y += k)
{
glVertex2i(x, int(y + 0.5));
}
}
//中点画线算法
void MidpointLine(int x0, int y0, int x1, int y1)
{
glColor3f(0.0, 1.0, 0.0); //设置像素点颜色为绿色
glPointSize(2.0f); //设置像素点大小
int a, b, d1, d2, d, x, y;
a = y0 - y1, b = x1 - x0, d = 2 * a + b;
d1 = 2 * a, d2 = 2 * (a + b);
x = x0, y = y0;
glVertex2f(x, y);
while (x < x1)
{
if (d < 0) {
x++, y++, d += d2;
}
else {
x++, d += d1;
}
glVertex2f(x, y);
}
}
//Bresenham算法画线
void BresenhamLine(int x0, int y0, int x1, int y1)
{
glColor3f(0.0, 0.0, 1.0); //设置像素点颜色为蓝色
glPointSize(2.0f); //设置像素点大小
int x, y, dx, dy, e;
dx = x1 - x0, dy = y1 - y0, e = -dx;
x = x0, y = y0;
for (int i = 0; i < dx; i++)
{
glVertex2f(x, y);
x = x + 1, e = e + 2 * dy;
if (e >= 0)
y++, e = e - 2 * dx;
}
}
void display()
{
int x1_1 = 20, y1_1 = 20, x2_1 = 160, y2_1 = 80;
int x1_2 = 20, y1_2 = 40, x2_2 = 160, y2_2 = 100;
int x1_3 = 20, y1_3 = 60, x2_3 = 160, y2_3 = 120;
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_POINTS);
DDALine(x1_1, y1_1, x2_1, y2_1);
glEnd();
glBegin(GL_POINTS);
MidpointLine(x1_2, y1_2, x2_2, y2_2);
glEnd();
glBegin(GL_POINTS);
BresenhamLine(x1_3, y1_3, x2_3, y2_3);
glEnd();
glFlush();
}
int main(int argc, char **argv)
{
glutInit(&argc, argv); //GLUT初始化
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); //指出显示窗口使用单个缓存且使用由红绿蓝三种颜色模型
glutInitWindowPosition(500, 500); //设置显示窗口位置
glutInitWindowSize(400, 400); //设置显示窗口大小
glutCreateWindow("Line Algorithm"); //设置显示窗口的标题
glutDisplayFunc(display); //将图赋值给显示窗口
glutReshapeFunc(ChangeSize);
init();
glutMainLoop(); //必须是程序的最后一个
return 0;
}
- 最终结果