直线算法
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算法比中点画线算法的应用范围更广。