【计算机图形学】直线的两种生成算法(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 controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

C#使用StackExchange.Redis实现分布式锁的两种方式介绍

《C#使用StackExchange.Redis实现分布式锁的两种方式介绍》分布式锁在集群的架构中发挥着重要的作用,:本文主要介绍C#使用StackExchange.Redis实现分布式锁的... 目录自定义分布式锁获取锁释放锁自动续期StackExchange.Redis分布式锁获取锁释放锁自动续期分布式

Windows 上如果忘记了 MySQL 密码 重置密码的两种方法

《Windows上如果忘记了MySQL密码重置密码的两种方法》:本文主要介绍Windows上如果忘记了MySQL密码重置密码的两种方法,本文通过两种方法结合实例代码给大家介绍的非常详细,感... 目录方法 1:以跳过权限验证模式启动 mysql 并重置密码方法 2:使用 my.ini 文件的临时配置在 Wi

IDEA自动生成注释模板的配置教程

《IDEA自动生成注释模板的配置教程》本文介绍了如何在IntelliJIDEA中配置类和方法的注释模板,包括自动生成项目名称、包名、日期和时间等内容,以及如何定制参数和返回值的注释格式,需要的朋友可以... 目录项目场景配置方法类注释模板定义类开头的注释步骤类注释效果方法注释模板定义方法开头的注释步骤方法注

Python如何自动生成环境依赖包requirements

《Python如何自动生成环境依赖包requirements》:本文主要介绍Python如何自动生成环境依赖包requirements问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录生成当前 python 环境 安装的所有依赖包1、命令2、常见问题只生成当前 项目 的所有依赖包1、

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

MySQL中动态生成SQL语句去掉所有字段的空格的操作方法

《MySQL中动态生成SQL语句去掉所有字段的空格的操作方法》在数据库管理过程中,我们常常会遇到需要对表中字段进行清洗和整理的情况,本文将详细介绍如何在MySQL中动态生成SQL语句来去掉所有字段的空... 目录在mysql中动态生成SQL语句去掉所有字段的空格准备工作原理分析动态生成SQL语句在MySQL

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n