Bresenham画直线算法(所有斜率)

Bresenham画直线算法(所有斜率)

目录问题描述Bresenham算法基本思想算法推演特殊斜率:0

Bresenham算法是图形学非常经典的光栅线生成算法,可用于显示直线、圆以及其他曲线。这里通过算法画直线过程,了解其工作原理。

问题描述

已知线段2端点\((x_0, y_0) (x_e, y_e)\),屏幕上画出该直线段。

由于屏幕是通过像素点显示的,只能通过像素点所在的整数坐标近似代表直线上点的位置。那么,该用什么点画出直线呢?这就是Bresenham算法要解决的问题。

Bresenham算法

基本思想

根据当前点,决定下一个(像素)点画在哪儿。要么在相邻点,要么在对角点。相邻点是指x坐标+1、y坐标不变,或者x不变、y+1;对角点是指x+1,同时y+1。

要确定2个备选点中的哪一个,可根据它们到直线的距离远近决定:如果相邻点更近,下一点选相邻点;如果对角点更近,下一点就选对角点。

为减少误差,用光栅直线逼近理论直线,Bresenham算法按变化大的轴(x轴或y轴)逐像素绘制线段。

算法推演

假设直线方程:\(y = mx + b\)

因为线段2端点在直线上,所以,\(y_0=mx_0+b, y_e=mx_e+b\)

线段x、y坐标变化:\(∆x=x_e-x_0, ∆y=y_e-y_0\)

斜率m、截距b:

\[m=∆y/∆x=(y_e-y_0)/(x_e-x_0 ), b=y_0-mx_0=y_0-x_0∆y/∆x \tag{1}

\]

如果现在画到了第k个像素点,位置\((x_k,y_k)\),那么第k+1个点\((x_{k+1},y_{k+1})\)在哪?

由于斜率影响算法的过程,可以分情况讨论:

特殊斜率:0

∵0 < m < 1

∴Δx>Δy

∴可按x轴递增绘制像素点,即\(x_{k+1}=x_k+1\);

∵m > 0且线段连续

∴y递增,\(y_{k+1}\)取值只能是\(y_k\)或\(y_k+1\)。

=> 第k+1点\((x_{k+1}, y_{k+1})\)有2个选择:\((x_k+1,y_k ) or (x_k+1,y_k+1)\)

tips: 哪个像素点距离直线更近,Bresenham算法就选择那个点作为下一个要绘制的点。然而,计算点到直线的距离方法复杂,而且涉及(斜率)浮点数运算。为了简化运算,Bresenham算法并没有用点到线的垂直距离,而是利用y轴或x轴方向的偏移替代。

根据\((1)\),直线在\(x_{k+1}\)位置的y值(理论值):

\[ y=mx_{k+1}+b=m(x_k+1)+b \tag{2}

\]

记2个备选点\(y_k, y_k+1\)到直线的竖直偏移分别为\(d_{1}, d_{2}\),有:

\[\tag{3}

\begin{aligned}

d_{1} & = y-y_k \\\\

& = m(x_k+1)+b-y_k

\end{aligned}

\]

\[\tag{4}

\begin{aligned}

d_{2} & = (y_k+1)-y \\\\

& = y_k+1-m(x_k+1)-b

\end{aligned}

\]

这里利用了m范围:0 < m < 1,有\(d_{1} > 0, d_{2} > 0\),可知,

\[y_{x+1}=\begin{cases}

y_k & d_{1} < d_{2}, y_k更近 \\\\

y_{k+1} & d_{1} ≥ d_{2}, y_{k+1}更近

\end{cases}

\]

要判断哪个点距离更近,将\((3)(4)\)做差值:

\[d_{1}-d_{2} = 2m(x_k+1)-2y_k+2b-1 \tag{5}

\]

其中,斜率m、截距b都是常数。

将\(m=∆y/∆x\)代入\((5)\),两边同时乘∆x,可得,

\[ ∆x(d_{1}-d_{2}) = 2∆y(x_k+1)-2∆xy_k+∆x(2b-1)

\]

令常数c:\(c=∆x(2b-1)\),可得第k步决策参数:

\[\tag{6}

\begin{aligned}

p_k & = ∆x(d_{1}-d_{2}) \\\\

& = 2∆y(x_k+1) - 2∆xy_k+c

\end{aligned}

\]

∵m > 0 ∴∆x>0 ∴\(p_k, d_{1}-d_{2}\)同号

问题转化为求\(p_k\)。

1)求\(p_k\)递推公式

根据\((6)\),当k取值k+1时,有

\[ p_{k+1} = 2∆y(x_{k+1}+1)-2∆xy_{k+1}+c \tag{7}

\]

\((7)-(6)\)可得递推公式,

\[ p_{k+1} - p_k = 2∆y(x_{k+1}-x_k)-2∆x(y_{k+1}-y_k) \tag{8}

\]

∵\(x_{k+1}=x_k+1\), \(y_{k+1}-y_k\)取值(0或1)取决于\(p_k\)的符号。

\[p_{k+1}=\begin{cases}

p_k+2∆y-2∆x & p_k ≥ 0 \\\\

p_k+2∆y & p_k < 0

\end{cases}

\]

2)求初值\(p_0\)

∵起始点\((x_0, y_0)\)在直线上

\[ \begin{aligned}

p_0 & = 2∆y(x_0+1)-2∆xy_0 + c \\\\

& = 2∆y(x_0+1)-2∆xy_0 + ∆x(2b-1)

\end{aligned}

\]

又\(b=y_0-mx_0=y_0-x_0∆y/∆x\) => \(∆xb=∆xy_0-x_0∆y\)

\[p_0=2∆y-∆x \tag{9}

\]

综上,可将0

输入线段2端点,左端点存储在\((x_0, y_0)\);

将\((x_0, y_0)\)装入帧缓存,画出第一个点;

计算常量\(∆x, ∆y, 2∆y, 2∆y-2∆x\),得到决策参数初值:\(p_0=2∆y-∆x\)

从k=0开始,沿着线段路径,每个\(x_k\)处,计算下一个要绘制的点位置:

如果\(p_k<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\);

重复步骤4),共计∆x-1次。

可写出0

void setPixel(int x, int y)

{

glBegin(GL_POINTS);

glVertex2i(x, y);

glEnd();

}

// case 0 < m < 1

void lineBresenham(int x0, int y0, int xe, int ye)

{

int dx = abs(xe - x0), dy = abs(ye - y0);

int p = 2 * dy - dx;

int twoDy = 2 * dy, twoDx = 2 * dx, twoDyMinusDx = 2 * (dy - dx);

int x, y;

/* determine which endpoint to use as a start point */

if (x0 > xe) {

x = xe;

y = ye;

xe = x0;

}

else {

x = x0;

y = y0;

}

setPixel(x, y); // draw pixel

while (x < xe) {

x++;

if (p < 0) {

p += twoDy;

}

else {

y++;

p += twoDyMinusDx;

}

setPixel(x, y);

}

}

其他斜率

简化一下推理过程。

当0

从像素点角度看,整个过程:

x: x0 -> xe, 递增(↑), x每次+1

y: y0 -> ye, 非递减(↑), y是否+1取决于p符号, 而p可由递推公式计算

有\(Δx > Δy\),可按x轴逐像素绘制,x每次+1

=> \(x_{k+1} = x_k + 1\)

∵y=f(x): y = mx + b连续且递增

∴\((x_k, y_k)\)下一像素点\((x_{k+1}, y_{k+1})\)选择:\((x_{k+1}, y_{k})\) or \((x_{k+1}, y_{k}+1)\)

=>

\[y_{k+1}=\begin{cases}

y_k & , y_k 更近 \\\\

y_k + 1 & , y_k + 1更近

\end{cases}

\]

\(x_{k+1}\)处垂直偏移\(d_{1}\), \(d_{2}\)分别用来(近似)代表点\((x_k+1, y_k)\), \((x_k+1, y_k+1)\)到直线的距离(均>=0)。

\[\begin{aligned}

d_{1} & = y(x_{k+1}) - y_k \\\\

& = (mx_{k+1} + b) - y_k \\\\

& = [m(x_k + 1) + b] - y_k

\end{aligned}

\]

\[\begin{aligned}

d_{2} & = y_{k+1} - y(x_{k+1}) \\\\

& = (y_k + 1) - (mx_{k+1} + b) \\\\

& = (y_k + 1) - [m(x_k + 1) + b]

\end{aligned}

\]

\(d_{1}, d_{2}\)做差值,

\[d_{1} - d_{2} = 2m(x_k+1) - 2y_k + 2b - 1

\]

两边乘以\(Δx\),可得决策参数\(p_k\):

\[\begin{aligned}

p_k & = Δx(d_{1} - d_{2}) = Δx[2m(x_k+1) - 2y_k + 2b - 1] \\\\

& = 2Δy(x_k+1) - 2Δxy_k + Δx(2b-1)

\end{aligned}

\]

k取k + 1,可得

\[\begin{aligned}

p_{k+1}-p_k &= 2Δy(x_{k+1} - x_k) - 2Δx(y_{k+1} - y_k) +0, \because b为常数 \therefore2b-1项做差值为0 \\\\

& = 2Δy - 2Δx(y_{k+1} - y_k)

\end{aligned}

\]

根据\(y_{k+1}\)与\(y_k\)关系,可得,

\[p_{k+1} - p_k = \begin{cases} 2Δy & , y_k更近, d_{low} < d_{upper}<=>p_k<0 \\\\

2Δy - 2Δx & , y_k+1更近, d_{low} > d_{upper}<=>p_k>0

\end{cases}

\]

注:Δx>0, Δy>0

而初值\(p_0\),

\[\begin{aligned}

p_0 &= [2Δy(x_0+1) - 2Δxy_0]+Δx(2b-1) \\\\

& = ... + 2Δxb - Δx , \because b = y - xΔy/Δx \therefore Δxb = Δxy - Δyx\\\\

& = ... + 2(Δxy_0 - Δyx_0) - Δx \\\\

& = 2Δy-Δx

\end{aligned}

\]

当-1

从像素点角度看,整个过程:

x: x0 -> xe, 递增(↑), x每次+1

y: y0 -> ye, 非递增(↓), y是否-1取决于p符号,p由递推公式计算

\((x_k, y_k)\)下一像素点:\((x_k + 1, y_k)\) or \((x_k + 1, y_k - 1)\)

\[\begin{aligned}

d_{1} &= y_k - y(x_k+1) \\\\

& = y_k - [m(x_k+1)+b]

\end{aligned}

\]

\[\begin{aligned}

d_{2} &= y(x_k+1)-(y_k-1) \\\\

& = m(x_k+1)+b - (y_k-1)

\end{aligned}

\]

做差值可得,

\[d_{1} - d_{2} = 2y_k - 2m(x_k+1) - 2b - 1

\]

发现与0

\[\begin{aligned}

p_k &= Δx(d_{1} - d_{2}) \\\\

& = 2Δxy_k-2Δy(x_k+1)-2Δxb-Δx

\end{aligned}

\]

将\(p_{k+1}\)与\(p_k\)做差值,求递推公式:

\[\begin{aligned}

p_{k+1} - p_k &= 2Δx(y_{k+1}-y_k) - 2Δy(x_{k+1}-x_k) + 0 \\\\

&=2Δx(y_{k+1}-y_k) - 2Δy, \because x_{k+1}-x_k=1 \\\\

&=\begin{cases}

-2Δy & , y_k更近<=>d_{1}p_k<0 \\\\

-2Δx-2Δy & , y_k-1更近<=>d_{1}>d_{2}<=>p_k<0

\end{cases}

\end{aligned}

\]

注: Δx>0, Δy<0

∵Δx>0

∴\(p_k\)与\(d_{1}-d_{2}\)同号

初值\(p_0\):\(p_0=-2Δy-Δx\)

综合 0

\[p_{k+1}-p_k=\begin{cases}

2|Δy| & , p_k < 0 \\\\

2|Δy|-2|Δx| & , p_k > 0

\end{cases}

\]

当m>1时,只需要将0

当m<-1时,只需要将-1

可以写出下面程序,适用于所有斜率的Bresenham算法:

// 在(x,y)位置绘制像素点

void set_pixel(int x, int y)

{

glBegin(GL_POINTS);

glVertex2i(x, y);

glEnd();

}

// 适用所有斜率的通用Bresenham画线算法

// (x1,y1) (x2, y2)是线段两端点

void bresenham_line(int x1, int y1, int x2, int y2)

{

int dx = x2 - x1;

int dy = y2 - y1;

int stepX = dx >= 0 ? 1 : -1;

int stepY = dy >= 0 ? 1 : -1;

dx = abs(dx);

dy = abs(dy);

if (dx > dy) { // |m| < 1

int p = 2 * dy - dx;

int y = y1;

for (int x = x1; x != x2; x += stepX) {

set_pixel(x, y);

if (p > 0) {

y += stepY;

p -= 2 * dx;

}

p += 2 * dy;

}

}

else { // |m| >= 1

int p = 2 * dx - dy;

int x = x1;

for (int y = y1; y != y2; y += stepY) {

set_pixel(x, y);

if (p > 0) {

x += stepX;

p -= 2 * dy;

}

p += 2 * dx;

}

}

}

参考

[1]DonaldHearn,M.PaulineBaker,赫恩,等.计算机图形学(第四版)[M].电子工业出版社,2014.

[2]KATEX公式编辑器符号大全-CSDN的Mardown公式支持 | CSDN

相关推荐

山羊形象为什么代表着恶魔?说说欧洲的恶魔学
365bet取款要多久到账

山羊形象为什么代表着恶魔?说说欧洲的恶魔学

📅 06-17 👁️ 9786
男变女变性手术费用、条件及优缺点全面解析
谤的意思,谤的解释,谤的拼音,谤的部首,谤的笔顺
第二个字是此的成语
365bet最快线路监测中心

第二个字是此的成语

📅 07-26 👁️ 3054
闪电降价怎么退货 闪电降价退货退款教程
365bet取款要多久到账

闪电降价怎么退货 闪电降价退货退款教程

📅 07-16 👁️ 683
PPT中如何让背景音乐播放和停止-PPT里添加的音乐如何暂停
365bet取款要多久到账

PPT中如何让背景音乐播放和停止-PPT里添加的音乐如何暂停

📅 10-10 👁️ 7359
DOCX转WORD
365bet最快线路监测中心

DOCX转WORD

📅 09-17 👁️ 9651
兩天後不要洗是騙人的?染髮後遵守這「8個方法」髮色持久不掉色
极速借钱APP是真的吗?
365bet最快线路监测中心

极速借钱APP是真的吗?

📅 07-05 👁️ 807
粥铺如何管理客户关系
365bet最快线路监测中心

粥铺如何管理客户关系

📅 01-31 👁️ 698
冰岛11号足球球员是谁 冰岛足球队队员兼职
365BET导航

冰岛11号足球球员是谁 冰岛足球队队员兼职

📅 01-02 👁️ 6946
炁学原理(科学诗)
365bet最快线路监测中心

炁学原理(科学诗)

📅 01-23 👁️ 6048