【计算机图形学】直线的两种生成算法(DDA算法、Bresenham算法)

2024-03-30 12:58

本文主要是介绍【计算机图形学】直线的两种生成算法(DDA算法、Bresenham算法),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

直线的两种生成算法(DDA算法、Bresenham算法)


文章目录

      • 1.计算机绘制直线的原理
      • 2.DDA算法的原理与实现(基于matlab)
      • 3.Bresenham算法的原理与实现(基于matlab)




1.计算机绘制直线的原理

在计算机中,直线的显示并不是连续的,而是离散的点,这是由光栅化的本质决定 的。我们可以把屏幕理解为阴极射线管光栅显示器,这个显示器是由离散可发光的有线 区域单元(像素)组成的矩阵。确定最佳逼近某直线的像素的过程通常叫做光栅化。对于 水平线、垂直线以及 45°线,选择哪些光栅元素是显而易见的,而对于其他方向的直线, 像素的选择就很困难。因此,选择一个重要的算法来实现直线的绘制显得很有必要。




2.DDA算法的原理与实现(基于matlab)

DDA 算法是直线算法里最为简单的算法,它的基本思想是高数里的微分算法:

d y d x = y 2 − y 1 x 2 − x 1 \frac{\mathrm{d} y}{\mathrm{d} x}=\frac{y_2-y_1}{x_2-x_1} dxdy=x2x1y2y1

其有限差分近似解为:

{ x i + 1 = x i + Δ x y i + 1 = y i + y 2 − y 1 x 2 − x 1 Δ x \begin{cases} x_{i+1} = x_i+\varDelta x \\ y_{i+1} = y_i+\frac{y_2-y_1}{x_2-x_1}\varDelta x \end{cases} {xi+1=xi+Δxyi+1=yi+x2x1y2y1Δx

根据这个算法,我们可以将某条线段的两个端点作为输入,并利用这两个端点来将此线段进行微分(迭代求值),在每轮迭代中,将微分得到的每个点单独绘制。最终,所有单独绘制的点就构成了我们所需要绘制的直线。


下面给出用matlab实现该算法的完整代码:

function main					%测试函数
clc
clear;
DDA(0,0,30,40,'r')    			%勾三股四弦五function DDA(x1,y1,x2,y2,color)		%DDA算法dx=(x2-x1);dy=(y2-y1);step=max(abs(dx),abs(dy));deltax=dx/step;deltay=dy/step;x=x1;y=y1;hold onfor i=1:stepscatter(round(x),round(y),'.',color)    	  %绘出当前点x=x+deltax;                            		  %更新xy=y+deltay;                             	  %更新yendplot([x1,x2],[y1,y2])                       	  %直接绘制原线以对比grid minor										  %添加网格hold off

运行后得到的效果如下:
Alt
上图展示的是在给定较短线段时(长度为50个像素),采用DDA算法绘出的直线情形(上图中的红点部分),其中的蓝色线段是这条线段本身(真实线段)
接下来若将该点设置长一些,即将上面的测试代码改为如下:

function main
clc
clear;
DDA(0,0,100,75,'r')	%勾三股四弦五

则此时的成像效果如下:
Alt
上图中的红点即为DDA算法绘制的给定线段,而其中的黄线则为真实线段
可以发现此时点的密集程度大大增加,成像效果相较于上面也就好一些。




3.Bresenham算法的原理与实现(基于matlab)

原理:
这里仅展示最简单的一种情况(直线斜率在区间 ( 0 , 1 ) (0,1) (0,1) 之间的),其他情况可以类似推导。
设当前像素点为 P ( x p , y p ) P(x_p,y_p) P(xp,yp),下一像素点有两种选择 P 1 P_1 P1 P 2 P_2 P2,设 P 1 、 P 2 P_1、P_2 P1P2 的中点为 M ( x p + 1 , y p + 0.5 ) M(x_{p+1}, y_{p+0.5}) M(xp+1,yp+0.5) Q Q Q 为所画直线与直线 x = x p + 1 x=x_{p+1} x=xp+1 交点,如下图所示:
在这里插入图片描述
Q Q Q M M M 上方时,下一像素点取 P 2 P_2 P2;当 Q Q Q M M M 下方时,下一像素点取 P 1 P_1 P1


实现:

  1. 首先设待画直线的起点和终点坐标分别为 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1), (x_2,y_2) (x1,y1),(x2,y2),则根据起点和中点坐标可以得到该直线的斜率为 k = y 2 − y 1 x 2 − x 1 k=\frac{y_2-y_1}{x_2-x_1} k=x2x1y2y1。我们知道直线方程可以用 F ( x , y ) = a x + b y + c F(x, y)=ax+by+c F(x,y)=ax+by+c 的形式来表达,此时该直线的斜率 k = − a b k=-\frac{a}{b} k=ba,那也就是说我们可以直接令 a = y 1 − y 2 , b = x 2 − x 1 a=y_1-y_2, b=x_2-x_1 a=y1y2,b=x2x1(只要满足商为 − k -k k 即可)。
    此时 F ( x , y ) = x ( y 1 − y 2 ) + y ( x 2 − x 1 ) + c F(x,y)=x(y_1-y_2)+y(x_2-x_1)+c F(x,y)=x(y1y2)+y(x2x1)+c,我们再随意带一个点进去(比如 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)),便可求得 c = x 1 y 2 − x 2 y 1 c=x_1y_2-x_2y_1 c=x1y2x2y1 ,即得到该直线的方程为:
    { F ( x , y ) = a x + b y + c = 0 a = y 1 − y 2 b = x 2 − x 1 c = x 1 y 2 − x 2 y 1 \begin{cases} F(x,y)=ax+by+c=0 \\ a=y_1-y_2 \\ b=x_2-x_1 \\ c=x_1y_2-x_2y_1 \end{cases} F(x,y)=ax+by+c=0a=y1y2b=x2x1c=x1y2x2y1

  2. 对于某个点 ( x , y ) (x,y) (x,y),我们要知道其与直线方程 F ( x , y ) F(x,y) F(x,y) 存在如下关系:
    { F ( x , y ) = 0 , 在直线上 F ( x , y ) > 0 , 在直线上方 F ( x , y ) < 0 , 在直线下方 \begin{cases} F(x,y)=0,& 在直线上 \\ F(x,y)>0,& 在直线上方 \\ F(x,y)<0,& 在直线下方 \\ \end{cases} F(x,y)=0F(x,y)>0F(x,y)<0在直线上在直线上方在直线下方
    因此,我们可以直接将中点 M ( x p + 1 , y p + 0.5 ) M(x_{p+1},y_{p+0.5}) M(xp+1,yp+0.5) 带入直线方程中:
    d = F ( x p + 1 , y p + 0.5 ) = a x p + 1 + b y p + 0.5 + c d=F(x_{p+1},y_{p+0.5})=ax_{p+1}+by_{p+0.5}+c d=F(xp+1,yp+0.5)=axp+1+byp+0.5+c,我们就可以根据 d d d 的值来确定下一个像素点:
    { 取 P 1 和 P 2 均可(此处取 P 1 ), d = 0 取 P 1 为下一像素点, d > 0 (表示 M 在直线上方) 取 P 2 为下一像素点, d < 0 (表示 M 在直线下方) \begin{cases} 取 P_1 和 P_2 均可(此处取 P_1),& d=0 \\ 取 P_1为下一像素点,& d>0(表示 M 在直线上方) \\ 取 P_2为下一像素点,& d<0(表示 M 在直线下方) \\ \end{cases} P1P2均可(此处取P1),P1为下一像素点,P2为下一像素点,d=0d>0(表示M在直线上方)d<0(表示M在直线下方)
    若设当前像素点为 P ( x p , y p ) P(x_p, y_p) P(xp,yp),我们考虑 d ≥ 0 d\ge0 d0 d < 0 d<0 d<0 两种情况:

  • d ≥ 0 d\ge0 d0,此时 P 1 ( x p + 1 , y p ) P_1(x_{p+1}, y_p) P1(xp+1,yp) 为下一像素点,则再下一像素点为 ( x p + 2 , y p + 0.5 ) (x_{p+2}, y_{p+0.5}) (xp+2,yp+0.5)
    d ′ = F ( x p + 2 , y p + 0.5 ) = a x p + 2 + b y p + 0.5 + c = a + ( a x p + 1 + b y p + 0.5 + c ) = a + d d'=F(x_{p+2}, y_{p+0.5})=ax_{p+2}+by_{p+0.5}+c=a+(ax_{p+1}+by_{p+0.5}+c)=a+d d=F(xp+2,yp+0.5)=axp+2+byp+0.5+c=a+(axp+1+byp+0.5+c)=a+d,此时 d ′ = a + d d'=a+d d=a+d,即增量为 a a a
  • d < 0 d<0 d<0,此时 P 2 ( x p + 1 , y p + 1 ) P_2(x_{p+1}, y_{p+1}) P2(xp+1,yp+1) 为下一像素点,则再下一像素点为 ( x p + 2 , y p + 1.5 ) (x_{p+2}, y_{p+1.5}) (xp+2,yp+1.5)
    d ′ ′ = F ( x p + 2 , y p + 1.5 ) = a x p + 2 + b y p + 1.5 + c = a + b + ( a x p + 1 + b y p + 0.5 + c ) = a + b + d d''=F(x_{p+2}, y_{p+1.5})=ax_{p+2}+by_{p+1.5}+c=a+b+(ax_{p+1}+by_{p+0.5}+c)=a+b+d d′′=F(xp+2,yp+1.5)=axp+2+byp+1.5+c=a+b+(axp+1+byp+0.5+c)=a+b+d,此时 d ′ ′ = a + b + d d''=a+b+d d′′=a+b+d,即增量为 a + b a+b a+b
  1. 注意到 d 0 d_0 d0 的初始化是用 ( x 1 , y 0.5 ) (x_1,y_{0.5}) (x1,y0.5) 来计算的:即 d 0 = a + 0.5 b + c d_0=a+0.5b+c d0=a+0.5b+c,且在后面的迭代中, y y y 始终都含有 0.5。而我们在迭代中判断下一个点纵坐标的取值时仅仅与 d d d 值的正负有关,因此为了摆脱浮点数的出现,我们统一将所有 d i d_i di 的计算都使用 2 d i 2d_i 2di 来代替,因此 d 0 = 2 a + b + 2 c d_0=2a+b+2c d0=2a+b+2c,增量 d ′ = 2 a d'=2a d=2a,增量 d ′ ′ = 2 a + 2 b d''=2a+2b d′′=2a+2b

下面给出用matlab实现该算法的完整代码:

function main         %测试代码
clc
clear;
Bresenham(0,0,50,40,'r')function Bresenham(x1,y1,x2,y2,color)		%Bresnham算法a = (y1-y2);b = (x2-x1);c = x1*y2-x2*y1;d=2*a+b+c;d1=2*a;d2=2*(a+b);x=x1;y=y1;hold onscatter(x,y,'.',color)for i=x:x2if d<0x=x+1;y=y+1;d=d+d2;elsex=x+1;d=d+d1;endscatter(x,y,'.',color)endplot([x1,x2],[y1,y2])                       %绘制原线以对比grid minorhold off

运行后得到的效果如下:
Alt
可以看出,中点算法在 x x x 轴上进行变化时, y y y上的取值严格按照 Bresenham 的原理进行取值。该算法作为数值微分法(DDA)的改进算法,其采用了直线的一般式方程、增量思想、实现整数加法等优化的思路,其成像的主要思想是以贴合直线为中心,使得这样的算法在绘制出的效果上是优于 DDA 算法的。


END


这篇关于【计算机图形学】直线的两种生成算法(DDA算法、Bresenham算法)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/861548

相关文章

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

java中使用POI生成Excel并导出过程

《java中使用POI生成Excel并导出过程》:本文主要介绍java中使用POI生成Excel并导出过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录需求说明及实现方式需求完成通用代码版本1版本2结果展示type参数为atype参数为b总结注:本文章中代码均为

在java中如何将inputStream对象转换为File对象(不生成本地文件)

《在java中如何将inputStream对象转换为File对象(不生成本地文件)》:本文主要介绍在java中如何将inputStream对象转换为File对象(不生成本地文件),具有很好的参考价... 目录需求说明问题解决总结需求说明在后端中通过POI生成Excel文件流,将输出流(outputStre

SpringBoot实现MD5加盐算法的示例代码

《SpringBoot实现MD5加盐算法的示例代码》加盐算法是一种用于增强密码安全性的技术,本文主要介绍了SpringBoot实现MD5加盐算法的示例代码,文中通过示例代码介绍的非常详细,对大家的学习... 目录一、什么是加盐算法二、如何实现加盐算法2.1 加盐算法代码实现2.2 注册页面中进行密码加盐2.

Docker镜像pull失败两种解决办法小结

《Docker镜像pull失败两种解决办法小结》有时候我们在拉取Docker镜像的过程中会遇到一些问题,:本文主要介绍Docker镜像pull失败两种解决办法的相关资料,文中通过代码介绍的非常详细... 目录docker 镜像 pull 失败解决办法1DrQwWCocker 镜像 pull 失败解决方法2总

在C#中调用Python代码的两种实现方式

《在C#中调用Python代码的两种实现方式》:本文主要介绍在C#中调用Python代码的两种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#调用python代码的方式1. 使用 Python.NET2. 使用外部进程调用 Python 脚本总结C#调

Java时间轮调度算法的代码实现

《Java时间轮调度算法的代码实现》时间轮是一种高效的定时调度算法,主要用于管理延时任务或周期性任务,它通过一个环形数组(时间轮)和指针来实现,将大量定时任务分摊到固定的时间槽中,极大地降低了时间复杂... 目录1、简述2、时间轮的原理3. 时间轮的实现步骤3.1 定义时间槽3.2 定义时间轮3.3 使用时

IDEA中Git版本回退的两种实现方案

《IDEA中Git版本回退的两种实现方案》作为开发者,代码版本回退是日常高频操作,IntelliJIDEA集成了强大的Git工具链,但面对reset和revert两种核心回退方案,许多开发者仍存在选择... 目录一、版本回退前置知识二、Reset方案:整体改写历史1、IDEA图形化操作(推荐)1.1、查看提

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现