操作系统:Windows8.1

显卡:Nivida GTX965M

开发工具:Unity2017.3 | NativeC


最近在学习 Unity tilemap Brush 自定义笔刷功能时候,看到其直线笔刷 LineBrush 是采用 Bresenham 算法实现,故借此机会在这里记录下学习过程,并在最后给出完整实现。

Introduction


Bresenham 是光栅化的直线算法,或者说是通过像素来模拟直线。比如下图所示像素点来模拟红色的直线。

给定两个起点 P1(x1, y1) | P2(x2, y2),如何绘制两点之间的连线呢。这里假设斜率约束在,那么算法的过程如下:

  1. 绘制起点 (x1, y1)
  2. 绘制下一个点, X坐标加1,判断是否到终点,如果是则算法完成。否则找下一个点,由上图可知将要绘制的点不是右邻点,要么就是右上邻接点。
  3. 绘制点。
  4. 跳回第二步骤。
  5. 结束。

算法具体过程就是在每次绘制点的时候选取与直线的交点y坐标的差最小的那个点,如下图所示:

那么问题聚焦在如何找最近的点,逻辑上每次 x 都递增 y 则增加 或不增加。具体上图,假设已经绘制到了 d1 点,那么接下来 x1,但是选择 d2 还是 u 点呢,直观上可以知道 d2 与目标直线和 x + 1 直线的交点比较近,即纵坐标之差小。换句话说 (x + 1, y + 1) 点纵坐标差大于 0.5。 所以选择 d2 ,其他点也按照该规则执行。

The Basic Bresenham


假设以 (x, y) 为绘制起点,一般情况下的直观想法是先求 m = dy /dxy 的增量),然后逐步递增 x, 设新的点为 x1 = x + j ,则 y1 = round(y + j * m) 。可以看到,这个过程涉及大量的浮点运算,效率上是比较低的(特别是在嵌入式应用中,DSP可以一周期内完成2次乘法,一次浮点却要上百个周期)。

下面我们来看一下 Bresenham 算法,如图1, (x, y +ε) 的下一个点为 (x, y + ε + m), 这里 ε 为累加误差。可以看出,当 ε+m < 0.5 时,绘制 (x + 1, y) 点,否则绘制 (x + 1, y + 1) 点。每次绘制后, ε 将更新为新值:

ε = ε + m ,如果 (ε + m) <0.5 (或表示为 2 * (ε + m) < 1 )

ε = ε + m – 1,其他情况

将上述公式都乘以 dx ,并将 ε * dx 用新符号 ξ 表示,可得

ξ = ξ + dy,如果 2 * (ξ + dy) < dx

ξ = ξ + dy – dx,其他情况

可以看到,此时运算已经全变为整数了。以下为算法的伪代码:

ξ ← 0,y ← y1

For x ← x1 to x2 do

  Plot Point at (x, y)

  If (2(ξ + dy) < dx)

    ξ ←ξ + dy

  Else

    y ← y + 1,ξ ←ξ + dy – dx

  End If

End For

Handing multiple slopes


在实际应用中,我们会发现,当 dy > dx 或出现上图右侧情况时,便得不到想要的结果,这是由于我们只考虑 dx > dy, 且 (x, y) 的增量均为正的情况所致。经过分析,需要考虑 8 种不同的分区情况,如下图所示:

当然,如果直接在算法中对8种情况分别枚举, 那重复代码便会显得十分臃肿,因此在设计算法时必须充分考虑上述各种情况的共性。比如右侧 X 正负 45 度分区仅仅是互为 Y 轴镜像的关系,在具体实现的时候设定正确增量方向即可。

Implementation


下面给出基于C语言的实现:

void draw_line(int x1, int y1, int x2, int y2)
{
int dx = x2 - x1;
int dy = y2 - y1;
int ux = ((dx > ) << ) - ;
int uy = ((dy > ) << ) - ;
int x = x1, y = y1, eps; eps = ; dx = abs(dx); dy = abs(dy);
if (dx > dy)
{
for (x = x1; x != x2; x += ux)
{
printf("x = %d y = %d\n", x, y);
eps += dy;
if ((eps << ) >= dx)
{
y += uy; eps -= dx;
}
}
}
else
{
for (y = y1; y != y2; y += uy)
{
printf("x = %d y = %d\n", x, y);
eps += dx;
if ((eps << ) >= dy)
{
x += ux; eps -= dy;
}
}
}
}

测试数据分别为绿色线段,起点 (1,1)  终点 (10,10)  和 蓝色线段,起点 (1, 10)  终点 (10,1) 。

测试数据分别为褐色线段,起点 (2,10)  终点 (4,1) 及起点 (6,9)  终点 (10,1)

通过将程序运行的测试数据填充的表格后,可以很直观的看到两点之间线段的连接路径。

Based on Unity


Unity 官方的例子中已经给出了基于C#实现,代码如下:

// http://ericw.ca/notes/bresenhams-line-algorithm-in-csharp.html
public static IEnumerable<Vector2Int> GetPointsOnLine(Vector2Int p1, Vector2Int p2)
{
int x0 = p1.x;
int y0 = p1.y;
int x1 = p2.x;
int y1 = p2.y; bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0);
if (steep)
{
int t;
t = x0; // swap x0 and y0
x0 = y0;
y0 = t;
t = x1; // swap x1 and y1
x1 = y1;
y1 = t;
}
if (x0 > x1)
{
int t;
t = x0; // swap x0 and x1
x0 = x1;
x1 = t;
t = y0; // swap y0 and y1
y0 = y1;
y1 = t;
}
int dx = x1 - x0;
int dy = Math.Abs(y1 - y0);
int error = dx / ;
int ystep = (y0 < y1) ? : -;
int y = y0;
for (int x = x0; x <= x1; x++)
{
yield return new Vector2Int((steep ? y : x), (steep ? x : y));
error = error - dy;
if (error < )
{
y += ystep;
error += dx;
}
}
yield break;
}

最后将之前的两组数据带入Unity验证结果一致性:

可以看到前文程序算法输出的数据结果与Unity采用的 C# 实现结果一致。

Summary


参考资料:

http://blog.csdn.net/jinbing_peng/article/details/44797993

https://www.cnblogs.com/gamesky/archive/2012/08/21/2648623.html

http://www.cnblogs.com/pheye/archive/2010/08/14/1799803.html

[AlgorithmStaff] Bresenham快速直线算法的更多相关文章

  1. Bresenham画直线,任意斜率

    function DrawLineBresenham(x1,y1,x2,y2) %sort by x,sure x1<x2. if x1>x2 tmp=x1; x1=x2; x2=tmp; ...

  2. Bresenham快速画直线算法

    现在的计算机的图像的都是用像素表示的,无论是点.直线.圆或其他图形最终都会以点的形式显示.人们看到屏幕的直线只不过是模拟出来的,人眼不能分辨出来而已.那么计算机是如何画直线的呢,其实有比较多的算法,这 ...

  3. Bresenham直线算法与画圆算法

    在我们内部开发使用的一个工具中,我们需要几乎从 0 开始实现一个高效的二维图像渲染引擎.比较幸运的是,我们只需要画直线.圆以及矩形,其中比较复杂的是画直线和圆.画直线和圆已经有非常多的成熟的算法了,我 ...

  4. Breaseman算法绘制直线算法公式推导|步骤|程序

    Breaseman算法绘制直线算法公式推导|步骤|程序 BreaseMan算法优点: (1)不必计算直线的斜率,因此不用做除法: (2)不用浮点数,只用整数: (3)制作整数的加减乘除,和乘2操作,乘 ...

  5. sdut 1592转置矩阵【稀疏矩阵的压缩存储】【快速转置算法】

    转置矩阵 Time Limit: 1000ms   Memory limit: 32768K  有疑问?点这里^_^ 题目链接:http://acm.sdut.edu.cn/sdutoj/proble ...

  6. 深度信任网络的快速学习算法(Hinton的论文)

    也没啥原创,就是在学习深度学习的过程中丰富一下我的博客,嘿嘿. 不喜勿喷! Hinton是深度学习方面的大牛,跟着大牛走一般不会错吧-- 来源:A fast learning algorithm fo ...

  7. Gamma原理及快速实现算法(C/C++)(转)

    源:Gamma原理及快速实现算法(C/C++) 原文:http://blog.csdn.net/lxy201700/article/details/24929013 参考 http://www.cam ...

  8. 《算法C语言实现》————快速-查找算法(quick-find algorithm)

    算法基础是一个整型数组,当且仅当第p个元素和第q个元素相等时,p和q时连通的.初始时,数组中的第i个元素的值为i,0<=i<N,为实现p与q的合并操作,我们遍历数组,把所有名为p的元素值改 ...

  9. DDA_为微分绘制直线算法

    DDA_为微分绘制直线算法 以步进坐标轴部长=1像素为单位,计算y=kx + b,绘制像素点(x, round(y)). 即步进坐标增长1, 另一坐标增长K或者1/k. 程序如下: //数值微分算法D ...

随机推荐

  1. day02Java基础学习笔记

    自动类型转换:容量小的类型自动转换为容量大的数据类型.数据类型按容量大小排序为: byte,short,char之间不会相互转换,他们三者在计算时首先转换为int类型 1.各种进制的表现形式和特点 二 ...

  2. promise的基础知识

    promise 相当于异步操作结果的占位符 它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个promise,例如: let promise = readFile('a.txt ...

  3. [置顶] Android 打包apk无敌报错

    前言: 这个问题从昨天上午一直到现在,请教了很多大佬,都没有给出确定的解决方案,可能他们也没碰到过可能,不过还是挺感谢他们的建议,一直到今天中午午休,我一直都在想这个问题,就是下面的这个,看了国内很多 ...

  4. Linux运维学习笔记-定时任务知识总结

    定时任务编辑规范流程: 重要知识点: 切记用全路径编写定时脚本.定时任务 大部分在 crontab 计划任务中都会年到未尾带 >/dev/null 2>&1,是什么意思呢? > ...

  5. .net 微信支付(公众号支付)遇到的问题

    啥也不说了搬砖的都知道老板说是什么就是什么 最近我老板让饿哦做一个微信支付的功能  还带微信上面京东众筹活动的那种,我买东西别人出钱的那种 然后用微信支付 我是新手之前也没有做过这个 所以估计着过程中 ...

  6. HDU 3973 AC's String 字符串哈希

    HDU 3973 通过哈希函数将一个字符串转化为一个整数,通过特定的方式可以使得这个哈希值几乎没有冲突(这就是关键之处,几乎没有视为没有= =!, 其实也可以考虑实现哈希冲突时的处理,只是这道题没必要 ...

  7. 【转】ubuntu 12.04 下 Vim 插件 YouCompleteMe 的安装

    原文网址:http://www.cnblogs.com/jostree/p/4137402.html 作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree ...

  8. mysql update 没有where 不能更新的安全保护设置

    http://www.cnblogs.com/wjoyxt/p/5620827.html    没有where 不能更新的安全保护设置 http://dev.yesky.com/429/3543292 ...

  9. 如何利用 AVDictionary 配置参数(转)

    本文是我的 FFMPEG Tips 系列的第五篇文章,准备介绍下 ffmpeg 提供的一个非常好用的健值对工具:AVDictionary,特别是对于没有 map 容器的 c 代码,可以充分利用它来配置 ...

  10. 自动获取 LDAP 基准 DN 列表

    问题描述 在使用 LDAP 协议从 Active Directory 等目录管理服务获取组织结构数据时,一般总是需要对目录的检索路径进行配置.但是由于实际使用中的目录组织结构通常会比较复杂,往往会出现 ...