计算机图形学:直线光栅化

直线在格化中进行扫描转换时,输入为A,B两点坐标,输出需为这条直线最贴合的中间点的坐标。
在这里插入图片描述

数值微分法(DDA):
因为直线的一阶导数是连续的,且 △ X和 △ Y是成比例的,所以我们可以在直线上无限加上无限小的 ε X和 ε Y来确认点。
当精度无限高时,可以绘制出毫无误差的直线,但设备的精度是有限的,没有必要那么精确。
为了保证栅格化直线的连续,我们选择 ε =1/max(|△x|,|△y|),即取跨度最大的X或Y的值来作为首先前进方向。
此时分为两种情况:
1.直线的斜率|k|<1:

此时|△y|>|△x|,所以我们选择y方向来作为ε的取值。
{

  • y1 = y ±1
  • x1 = x±1/k

}
2.直线的斜率|k|>1:
此时|△x|>|△y|,所以我们选择x方向来作为ε的取值。
{

  • x1 = x±1
  • y1 = y±k

}
在这里插入图片描述

代码实现则为:

private void DrawLine_Rasterization(Vector2 A, Vector2 B)
{
    Graphics g = this.CreateGraphics();
    Pen pen = new Pen(Color.Red, 1);
    Pen linepen = new Pen(Color.Yellow);
    var k = (B.Y - A.Y) / (B.X - A.X);
    if (Math.Abs(k)>=1)
    {
        var x0 = A.X;
        var y0 = A.Y;
        while (y0 <B.Y)
        {
            var y1 = y0 + 1f;
            var x1 =  x0 + (1 / k);
            var roundx = Convert.ToInt32(x1);
            Point pos = MoveToCoordinate(new Vector2(roundx, y1));
            g.DrawRectangle(pen, pos.X - coord, pos.Y - coord, coord, coord);

            g.DrawLine(linepen, MoveToCoordinate(new Vector2(x0, y0)), pos);
            x0 = x1;
            y0 = y1;
        }
    }
    else
    {
        var x0 = A.X;
        var y0 = A.Y;
        while (y0 < B.Y)
        {
            var y1 = y0 + k;
            var x1 = x0 + 1f;
            var roundy = Convert.ToInt32(y1);
            Point pos = MoveToCoordinate(new Vector2(x1, roundy));
            g.DrawRectangle(pen, pos.X - coord, pos.Y - coord, coord, coord);

            g.DrawLine(linepen, MoveToCoordinate(new Vector2(x0, y0)), pos);
            x0 = x1;
            y0 = y1;
        }
    }
}

此算法优点:直观,容易实现
缺点:有浮点数和浮点运算,性能损耗大

Bresenham中点算法
在这里插入图片描述

给与的任何直线,我们都可以用 y=kx+b的方式来表示。
即,在平面坐标中,对于任何一点(x,y),可以用F(x,y)来判断点与直线的位置关系。
F(x,y)>0则点在直线上方
F(x,y)=0则在直线上
F(x,y)<0则在直线下方

首先以k<1的时候来推算,会比较方便

中点Bresenham算法的基本原理:
和数值微分法DDA相似,都会使用k与1的大小关系来判断向x方向还是向y方向扫描。
为了讨论方便,首先假设0<k<1。
如图,在向X方向扫描后,可以检测到F(x+1)位于pu和pd之间,Bersenham引入了pu和pd的中点M(x + 1,F(x) + 0.5),来作为判断该像素绘制在pd点或是pu点的依据。
即,若|pu-Q|>|pd-Q| (M点在直线上方)则取pu,反之(M点在直线下方)则取pd。
M点的坐标为(x+1,y+0.5),将此坐标带入F(x,y)则可以取出点M与直线的位置关系:
d = y+0.5-k(x+1) - b
即,当d<0时,M在直线下方,我们取上方的点pu,则y1=y+1
当d>0时,M在直线上方,我们取下方的点pd,则y1=y
d=0时,M在直线中,取哪个都行。
在这里插入图片描述

误差项递推
由于像素点和数据点之间会有误差,所以我们必须使用上一次得出的d来规避误差
为了讨论方便,首先假设0<=k<=1。

此时的误差项递推也需要分为两种情况
1.上一次的d<0时:

我们在上一次扫描中,因为d<0,进行了y1 = y + 1的操作,则这一次的判断中,M点的坐标为(x+2,y+1.5)
此时将公式代入可得出:
d = y+1.5 - k(x+2) - b
和上面得出的上一轮M的坐标对比可得到d的增量为:
△ d = 1 - k

在这里插入图片描述

2.上一次的d>0时:

和1同理,可得出M点的坐标为(x+2,y+0.5)
此时将公式代入:
d = y+0.5 - k(x+2) - b
可得出d的增量为:
△d = - k

在这里插入图片描述

d的初值
为了讨论方便,首先假设0<=k<=1。

d的初值为从初始点扫描起的第一个M点,即(x+1,y+0.5)
公式代入得:
d = 0.5-k

从这里就可以对运算中的浮点数计算进行优化,这就是比DDA灵活的地方:
我们只运用到了d的符号,并没有运用过d的实际大小,所以我们把d放大任意整数倍,仍然不会影响到我们计算的结果。
对于初值 d=0.5-k,我们把d放大2倍
对于所有的k,已知k=△y/△x,x与y均为整数
我们可以放大△x倍
则d的初值:
d =△x -2△y
d<0时的增量:
△d = 2△x-2△y
d>0时的增量:
△d = -2△y

可以看到,我们忽略了k<0和k>1的情况,这个情况我们只需要把坐标系旋转到满足0<k<1即可
代码:

        var k = ((float)B.Y - (float)A.Y) / ((float)B.X - (float)A.X);
        var dx = Math.Abs(B.X - A.X);var dy = Math.Abs(B.Y - A.Y);
        int startd = 0;int lessd = 0;int mored = 0;
        bool swichxy = false;
        int yoffset = 1;
        if (dy>dx)
        {
            var temp = A.Y;A.Y = A.X;A.X = temp;
            temp = B.Y;B.Y = B.X;B.X = temp;
            dx = Math.Abs(B.X - A.X); dy = Math.Abs(B.Y - A.Y);
            swichxy = true;
        }
        //交换AB,确保B的X比A大
        if (A.X > B.X)
        {
            var temp = A;
            A = B;
            B = temp;
        }
        if (B.Y <= A.Y)
        {
            yoffset = -1;
        }
        var x0 = A.X; var y0 = A.Y;
        startd = dx - 2 * dy;
        lessd = 2 * dx - 2 * dy;
        mored = -2 * dy;
        while (x0 < B.X)
        {
            var x1 = x0 + 1;
            var y1 = y0 + (startd < 0 ? yoffset : 0);
            startd = startd + (startd < 0 ? lessd : mored);
            Point pos = MoveToCoordinate(new Vector2(x1, y1));
            if (swichxy)
            {
                pos = MoveToCoordinate(new Vector2(y1, x1));
            }
            g.DrawRectangle(pen, pos.X - coord, pos.Y - coord, coord, coord);
            //g.DrawLine(linepen, MoveToCoordinate(new Vector2(x0, y0)), pos);
            x0 = x1;
            y0 = y1;
        }

Bresenham算法
在bresenham中点算法中,我们引入了中点,候选点,候选点中点,只是为了引入误差项这个概念.
我们可以直接一点,直接引入更简洁的误差项:
在这里插入图片描述
通过d与0.5的比较,可以达到和中点Bresenham中D相同的答案
此时
d0=0
Δd=k
x1=x+1
d>0.5时y1=y+1 此时d=d-1
d<0.5时y1=y
d=0.5时,任取
这里可以看到算法中仍有浮点运算
我们令e=d-0.5,然后乘2Δx,可以消除掉这些浮点

e0=-Δx
Δe=k →2Δy
e>0时y1=y+1此时e = e-2Δx
e<0时y1=y
e=0时,任取

如图
在这里插入图片描述

代码:

        var k = ((float)B.Y - (float)A.Y) / ((float)B.X - (float)A.X);
        var dx = Math.Abs(B.X - A.X); var dy = Math.Abs(B.Y - A.Y);
        int counter = 0;int de = 0;int e = 0;
        bool swichxy = false;
        int yoffset = 1;
        if (dy > dx)
        {
            var temp = A.Y; A.Y = A.X; A.X = temp;
            temp = B.Y; B.Y = B.X; B.X = temp;
            dx = Math.Abs(B.X - A.X); dy = Math.Abs(B.Y - A.Y);
            swichxy = true;
        }
        //交换AB,确保B的X比A大
        if (A.X > B.X)
        {
            var temp = A;
            A = B;
            B = temp;
        }
        if (B.Y <= A.Y)
        {
            yoffset = -1;
        }
        var x0 = A.X; var y0 = A.Y;
        counter = -2 * dx;
        de = dx - 2 * dy;
        e =-1*dx;
        while (x0 < B.X)
        {
            Point pos = MoveToCoordinate(new Vector2(x0, y0));
            if (swichxy)
            {
                pos = MoveToCoordinate(new Vector2(y0, x0));
            }
            g.DrawRectangle(pen, pos.X, pos.Y - coord , coord, coord);
            //g.DrawLine(linepen, MoveToCoordinate(new Vector2(x0, y0)), pos);
            var x1 = x0 + 1;
            var y1 = y0;
            e = e + 2 * dy;
            //这里选择e=0时y=y
            if (e > 0)
            {
                y1 = y0 + yoffset;
                e = e - 2 * dx;
            }
            x0 = x1;
            y0 = y1;
        }
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值