DDA画线算法与Bresenham画线算法

文章目录

  • DDA画线算法
      • 定义
      • 公式推导
      • 代码实现
      • 效果
      • 缺点
    • Bresenham画线算法
      • 定义
      • 公式推导
      • 代码实现
      • 效果
    • 源码地址

DDA画线算法

定义

通过单位间隔来确定线段的点,默认间隔为1,如图:DDA画线算法与Bresenham画线算法_第1张图片
此时,已知的点为 ( x 0 , y 0 ) , ( x e n d , y e n d ) (x_0,y_0), (x_{end},y_{end}) (x0,y0),(xend,yend),假设 y k 与 y k + 1 y_k与y_{k+1} ykyk+1是两点连线上的点的纵坐标

公式推导

设直线方程为: y = m x + b , m = y e n d − y 0 x e n d − x 0 y = mx + b,m=\frac{y_{end}-y0}{x_{end}-x_0} y=mx+b,m=xendx0yendy0
此时进行分类讨论:
当m大小小于等于1时,则 x x x每次递增1,即 Δ x = 1 \Delta x=1 Δx=1, y y y每次递增为 m m m,公式如下:
y k + 1 = y k + m Δ x ( Δ x = 1 ) y_{k+1} = y_k + m\Delta x(\Delta x=1) yk+1=yk+mΔx(Δx=1)
反之,当m大小大于1时,则 y y y每次递增1,即 Δ y = 1 \Delta y=1 Δy=1, x x x每次递增为 1 m \frac{1}{m} m1,
x k + 1 = x k + 1 m Δ y ( Δ y = 1 ) x_{k+1}=x_k+\frac{1}{m}\Delta y(\Delta y=1) xk+1=xk+m1Δy(Δy=1)

代码实现

#include 
#include 
#include 
#include 
#include 
#include 

// DDA算法
void lineDDA(int x0, int y0, int xend, int yend) {
	int dx = xend - x0, dy = yend - y0; // 两点间的横,纵坐标间隔
	float xIncrement, yIncrement, x = x0, y = y0; // 初始化x,y
	int steps;

	if (fabs(dx) >= dy) { // 当斜率小于等于1时
		steps = fabs(dx);
	}
	else // 当斜率大小大于1时
	{
		steps = fabs(dy);
	}
	// 设置 Δx, Δy 的大小
	xIncrement = float(dx) / float(steps);
	yIncrement = float(dy) / float(steps);
	
	//开始画点
	glClear(GL_COLOR_BUFFER_BIT);
	glBegin(GL_POINTS);
	glVertex2i(x0, y0);
	for (int i = 0; i < steps; i++)
	{
		x += xIncrement;
		y += yIncrement;
		glVertex2f(x, y);
	}
	glVertex2i(xend, yend);
	glEnd();
	glFlush();
}

// 主方法
void display() {
	glClear(GL_COLOR_BUFFER_BIT);
	glViewport(100, 100, 500, 500);
	lineDDA(30, 35, 140, 200);
}

// 初始化该方法
void Init() {
	glClearColor(1.0, 1.0, 1.0, 0.0);
	glPointSize(5.0);
	glColor3f(0.0, 0.0, 0.0);
	gluOrtho2D(0.0, 600.0, 0.0, 600.0);
	glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char* argv[]) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	glutInitWindowSize(600, 600);
	glutInitWindowPosition(0, 0);
	glutCreateWindow("lineDDA");
	glutDisplayFunc(display);
	Init();
	glutMainLoop();

	return 0;
}



效果

DDA画线算法与Bresenham画线算法_第2张图片

缺点

在浮点增量的连续叠加中,会造成取整的误差问题,是像素位置偏离实际线段,而且该过程的取整与浮点运算仍十分耗时

Bresenham画线算法

定义

该算法属于增量整数计算,不会涉及浮点数的叠加问题.Bresenham算法采用的是逐步递推的方法来确定下一个像素点的位置.注:像素点只能取整数坐标!!!!!

公式推导

  • 首先我们来看一个示例:
    DDA画线算法与Bresenham画线算法_第3张图片
    在该图中只能取整数点的像素坐标, 由于第k个像素点与 ( x k , y k ) (x_k,y_k) (xk,yk)点最为接近,所以先假设第k个像素点坐标为 ( x k , y k ) (x_k,y_k) (xk,yk),那么第k+1个像素点的位置可能为 ( x k + 1 , y k ) 或 ( x k + 1 , y k + 1 ) (x_{k+1},y_k)或(x_{k+1},y_{k+1}) (xk+1,yk)(xk+1,yk+1),这是我们需要计算该点与上述两点哪个最为接近.
  • 我们知道该直线方程为: y = m x + b y=mx+b y=mx+b, Δ y 与 Δ x \Delta y 与\Delta x ΔyΔx为两端点垂直与水平偏移量, m = Δ y Δ x m=\frac{\Delta y}{\Delta x} m=ΔxΔy,那么在 x k + 1 x_{k+1} xk+1处的y的坐标为: y = m ( x k + 1 ) + b = m ( x k + 1 ) + b y=m(x_{k+1})+b=m(x_k+1)+b y=m(xk+1)+b=m(xk+1)+b
    • 该点到 y k y_k yk的距离为: d l o w e r = y − y k = m ( x k + 1 ) + b − y k d_{lower}=y-y_k=m(x_k+1)+b-y_k dlower=yyk=m(xk+1)+byk
    • 该点到 y k + 1 y_k+1 yk+1的距离为: d u p p e r = y k + 1 − y = ( y k + 1 ) − y = ( y k + 1 ) − m ( x k + 1 ) − b d_{upper}=y_{k+1}-y=(y_k+1)-y=(y_k+1)-m(x_k+1)-b dupper=yk+1y=(yk+1)y=(yk+1)m(xk+1)b
  • 然后我们就可以比较他们距离的大小 d l o w e r − d u p p e r = 2 m ( x k + 1 ) − 2 y k + 2 b − 1 d_{lower}-d_{upper}=2m(x_k+1)-2y_k+2b-1 dlowerdupper=2m(xk+1)2yk+2b1
    • 若结果小于0,说明跟下面的像素点最接近,就选择 ( x k + 1 , y k ) (x_{k+1},y_k) (xk+1,yk)
    • 大于等于0,则选择 ( x k + 1 , y k + 1 ) (x_{k+1},y_{k+1}) (xk+1,yk+1)
  • 通过上述公式的对比可以发现,像素点的选择只取决于 ( d l o w e r − d u p p e r ) (d_{lower}-d_{upper}) (dlowerdupper)的符号且通过 ( x k , y k ) (x_k,y_k) (xk,yk)便可以确定下一个点的位置,为了方便我们的计算,引入了新的变量 p k p_k pk,作为我们的决策参数
  • p k = Δ x ( d l o w e r − d u p p e r ) = 2 Δ y ∗ x k − 2 Δ x ∗ y k + 2 Δ y + Δ x ( 2 b − 1 ) p_k=\Delta x(d_{lower}-d_{upper})=2\Delta y*x_k-2\Delta x*y_k +2\Delta y+\Delta x(2b-1) pk=Δx(dlowerdupper)=2Δyxk2Δxyk+2Δy+Δx(2b1),其中 2 Δ y + Δ x ( 2 b − 1 ) 2\Delta y+\Delta x(2b-1) 2Δy+Δx(2b1) 为常数,我们用 c c c来代替,于是 p k = Δ x ( d l o w e r − d u p p e r ) = 2 Δ y ∗ x k − 2 Δ x ∗ y k + c p_k=\Delta x(d_{lower}-d_{upper})=2\Delta y*x_k-2\Delta x*y_k +c pk=Δx(dlowerdupper)=2Δyxk2Δxyk+c 注:由于Δ x>0,所以对pk的正负符号无影响
  • 同理 p k + 1 = 2 Δ y ∗ ( x k + 1 ) − 2 Δ x ∗ y k + 1 + c ( x k + 1 = x k + 1 ) p_{k+1} =2\Delta y*(x_{k+1})-2\Delta x*y_{k+1}+c(x_{k+1}=x_k+1) pk+1=2Δy(xk+1)2Δxyk+1+c(xk+1=xk+1)
  • p k + 1 − p k = 2 Δ y ( x k + 1 − x k ) − 2 Δ x ( y k + 1 − y k )    ⟹    p k + 1 = p k + 2 Δ y ( x k + 1 − x k ) − 2 Δ x ( y k + 1 − y k ) = p k + 2 Δ y − 2 Δ x ( y k + 1 − y k ) p_{k+1}-p_k =2\Delta y(x_{k+1}-x_k)-2\Delta x(y_{k+1}-y_k) \implies p_{k+1}=p_k +2\Delta y(x_{k+1}-x_k)-2\Delta x(y_{k+1}-y_k) =p_k+2\Delta y-2\Delta x(y_{k+1}-y_k) pk+1pk=2Δy(xk+1xk)2Δx(yk+1yk)pk+1=pk+2Δy(xk+1xk)2Δx(yk+1yk)=pk+2Δy2Δx(yk+1yk)
    • p k > 0 , y k + 1 = y k p_k>0,y_{k+1}=y_k pk>0,yk+1=yk
    • p k < = 0 , y k + 1 = y k + 1 p_k<= 0,y_{k+1} = y_k+1 pk<=0,yk+1=yk+1
  • 求初始值 p 0 = 2 Δ y ∗ x 0 − 2 Δ x ∗ y 0 + 2 Δ y + Δ x ( 2 b − 1 ) = 2 m x 0 Δ x − 2 Δ x ( m x 0 + b ) + 2 Δ y + Δ x ( 2 b − 1 ) = 2 Δ y − Δ x p_0=2\Delta y*x_0-2\Delta x*y_0+2\Delta y+\Delta x(2b-1)=2mx_0\Delta x-2\Delta x(mx_0+b)+2\Delta y+\Delta x(2b-1)=2\Delta y-\Delta x p0=2Δyx02Δxy0+2Δy+Δx(2b1)=2mx0Δx2Δx(mx0+b)+2Δy+Δx(2b1)=2ΔyΔx
  • ∣ m ∣ < 1 时 的 B r e s e n h a m 算 法 流 程 |m|<1时的Bresenham算法流程 m<1Bresenham:
是 k++
开始
输入两个端点,且左端点为x0,y0
将x0,y0装入帧缓存,画出第一个点
计算常量Δx,Δy,2Δy,2Δy-2Δx,并获取:p0=2Δy-Δx
pk<0?
画出下一个点x_`k+1`,y_k,并且p_`k+1`=p_k+2Δy
画出下一个点x_`k+1`,y_`k+1`,并且p_`k+1`=p_k+2Δy-2Δx
k<=Δx-1?
结束

代码实现

#include 
#include 
#include 
#include 
#include 
#include 

// Bresenham 算法
void lineBresenham(int x0, int y0, int xend, int yend) {
	int dx = fabs(xend - x0), dy = fabs(yend - y0);
	int p = 2 * dy - dx;
	int twoDy = 2 * dy, twoDyMinusDx = 2 * (dy - dx);
	int x, y;
	// 确定开始位置
	if (x0 > xend) // x0在右侧
	{
		x = xend;
		y = yend;
		xend = x0;
	}
	else // x0在左侧
	{
		x = x0;
		y = y0;
	}
	glClear(GL_COLOR_BUFFER_BIT);
	glBegin(GL_POINTS);
	glVertex2f(x, y);
	while (x

效果

DDA画线算法与Bresenham画线算法_第4张图片

源码地址

你可能感兴趣的:(计算机图形学)