[计算机图形学基础]Bresenham直线绘制算法

Abstract

由于毕业设计的原因,复习一下大三学的一个直线绘制算法。


中点Bresenam算法

首先我们有了直线两端点 (A(x_{0}, y_{0}))(B(x_{1}, y_{1})),并设直线方程为

(F(x, y) = y - kx - b),由两端点容易得到斜率 (k = frac{y_{1} - y_{0}}{x_{1} - x_{0}})

先提前引入一个事实:将任意一个点带入(F(x, y))后,若(F(x, y) > 0)说明此点在直线上方;若(F(x, y) < 0)说明在直线下方;若等于0则表示点刚好落在直线上。


Bresenham的思想就是,每次在最大位移方向上前进一个单位,而在另一个方向上是否前进取决于判别式。至于为什么要选择最大位移方向,会涉及到直线的反走样,详见我

对DDA算法解析的文章数值微分直线生成算法(DDA)

(0 leq k leq 1)的情形

此时截距(x_{1} - x_{0} > y_{1} - y_{0}),也即最位移方向为X轴正向。因此在计算下一个点(x_{i+1})时,在(X)方向行进1,需确定下个点的(y)值是否加1:

如图,我们的下一个理想绘制点其实应该是直线与(x = x_{i} + 1)的交点(Q),但是由于像素是离散的,仅有整数值,我们只能将这个交点近似地交由(Q)上面或下面的离散点来表

示。判断方法很简单,看交点(Q)在两个备选点的中点(M)的上面还是下面,反过来判断即为看中点(M)(Q)的上面还是下面,若在(Q)的上面,则下一个点的(y)不变;若在(Q)下面,

则下一个点的(y)移动一个步长。

判别式(d_{i} = F(x_{M}, y_{M}) = F(x_{i} + 1, y_{i} + 0.5) = y_{i} + 0.5 - k(x_{i} + 1) - b)

此时可将中点(M(x_{i} + 1, y_{i} + 0.5))带入(F(x, y)),若结果(d_{i} > 0),表示点(M)(Q)之上,此时下一个绘制点确定为((x_{i} + 1, y_{i}));反之确定为((x_{i}+1, y_{i}+1));若(d_{i} = 0),表示

Q正好和M重合,那么我们默认下一个点取(y)不变的那个:

(x_{i+1} = x_{i} + 1)

( egin{equation}y_{i+1} = egin{cases} y_{i} + 1 & (d_{i} < 0)\ y_{i} & (d_{i} geq 0) end{cases} end{equation} )


判别式的推导

计算下一个绘制点时:

(d_{i} < 0)

取右上方点(P_{u}(x_{i} + 1, y{i} + 1))。现在再判断下下一个点如何计算,即:

( egin{equation} egin{aligned} d_{i+1} &= F(x_{i} + 2, y_{i} + 1.5) \ &= y_{i} + 1.5 - k(x_{i} + 1) - b - k \ &= di + 1 - k end{aligned} end{equation} )

也即当(d_{i} < 0)时,(d_{i+1})对于({d_{i}})的增量为(1-k)


(d_{i} geq 0)

取正右方点(P_{d}(x_{i} + 1, y{i}))。现在再判断下下一个点如何计算,即:

( egin{equation} egin{aligned} d_{i+1} &= F(x_{i} + 2, y_{i} + 0.5) \ &= y_{i} + 0.5 - k(x_{i} + 1) - b - k \ &= di - k end{aligned} end{equation} )

也即当(d_{i} geq 0)时,(d_{i+1})对于({d_{i}})的增量为(-k)


代码中当然需要得到(d_{i})的初值。显然直线的第一个像素(p_{0})一定在直线上,因此可计算:

( egin{equation} egin{aligned} d_{0} &= F(x_{0} + 1, y_{0} + 0.5) \ &= y_{0} - kx_{0} - b - k + 0.5 \ &= 0.5 - k end{aligned} end{equation} )

注意这里的推导,由(p_{0})在直线上的原因,(y_{0} - kx_{0} - b = 0)

看看我们推出了哪些需要的东西:

(d_{i} < 0)时,(d_{i+1})对于(d_{i})的增量((1)1-k),进一步写作(1-frac{Delta y}{Delta x})

(d_{i} geq 0)时,(d_{i+1})对于(d_{i})的增量((2)-k),进一步写作(-frac{Delta y}{Delta x})

以及(d_{0} = 0.5 - k),进一步写作(0.5 - frac{Delta y}{Delta x})

我们希望尽量用整数来进行运算以加快速度,所以对这三个变量同时乘以(2)再乘以(Delta x)来消除浮点运算:

(1) (2Delta x-2Delta y) (2) (-2Delta y) (3) (Delta x - 2Delta y)

现在可以用代码来表示这个算法了:((0 leq k leq 1)

void Bresenham(Vector3f begin, Vector3f end)
{
    float x0 = begin.x();
    float y0 = begin.y();
    float x1 = end.x();
    float y1 = end.y();

    // 确保计算方向为正向
    if(x0 > x1) {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }

    int dX = std::round(fabs(x1 - x0));
    int dY = std::round(fabs(y1 - y0));
    int delta = dX - 2 * dY;

    int dStepUp = 2 * (dX - dY);
    int dStepDown = -2 * dY;

    int x = x0, y = y0;

    for(int i = x; i <= x1; i++) {
        set_pixel(x, y);
        if(delta < 0) {
            y += 1;
            delta += dStepUp;
        } else {
            delta += dStepDown;
        }
    }
}
原文地址:https://www.cnblogs.com/1Kasshole/p/14038274.html