美文网首页
图形学自问自答1——直线光栅化

图形学自问自答1——直线光栅化

作者: 太刀 | 来源:发表于2021-01-28 00:09 被阅读0次
《底特律变人》角色卡拉

如何在屏幕上画线段

光栅化是一个连续数据离散化的过程,而最简单的一个连续数据离散化的过程是如何将一条线段在屏幕中画出来

注:为简化分析和处理,本文只考虑斜率 k>=0的直线,对于k<0的情形,可以很容易添加对应的代码进行处理。

1. 直接计算算法

直接根据直线的斜率表达式y=kx+b来计算,若在水平方向的变化更大,则针对每个x计算对应的y,否则针对每个y计算对应的x,然后绘制像素即可。代码如下:

    // 暴力算法
    public void DrawLineSimple(Vector2 start, Vector2 end, Color color)
    {
        int startX = Mathf.RoundToInt(start.x);
        int endX = Mathf.RoundToInt(end.x);
        int startY = Mathf.RoundToInt(start.y);
        int endY = Mathf.RoundToInt(end.y);

        // 无法计算斜率k的情况
        if (startX == endX)
        {
            int x = Mathf.RoundToInt(start.x);
            for (int i = startY; i <= endY; i++)
            {                
                screen.SetPixel(x, i, color);
            }
        }
        else
        {
            float k = (end.y - start.y) / (end.x - start.x);
            float b = end.y - k * end.x;
            
            if (0<k && k<1) // 只考虑这个斜率范围,其它情况修改参数可以很简单实现
            {
                for (int i = startX; i <= endX; i++)
                {
                    int y = Mathf.RoundToInt(i * k + b);
                    screen.SetPixel(i, y, color);
                }
            }            
        }
    }

2. DDA 算法

在上面的直接计算算法中,应用公式 y=kx+b考虑在对x进行行进时,有y_{n+1}=kx_{n+1}+b=k(x_n+1)+b=kx_n+b+k=y_n+k也就是
y_{n+1}=y_n+k将乘法消灭了,算法只剩下加法,代码如下:

    // DDA 算法
    public void DrawLineDDA(Vector2 start, Vector2 end, Color color)
    {
        int startX = Mathf.RoundToInt(start.x);
        int endX = Mathf.RoundToInt(end.x);
        int startY = Mathf.RoundToInt(start.y);
        int endY = Mathf.RoundToInt(end.y);

        // 无法计算斜率k的情况
        if (startX == endX)
        {
            int x = Mathf.RoundToInt(start.x);
            for (int i = startY; i <= endY; i++)
            {           
                screen.SetPixel(x, i, color);
            }
        }
        else
        {
            float k = (end.y - start.y) / (end.x - start.x);
            float b = end.y - k * end.x;
            if (0 < k && k < 1) // 只考虑这个斜率范围,其它情况修改参数可以很简单实现
            {
                float y = start.y;
                for (int i = startX; i <= endX; i++)
                {
                    y += k;
                    screen.SetPixel(i, Mathf.RoundToInt(y), color);
                }
            }
        }
    }

3. 中点画线算法

画线示意

考虑图中的直线,假设目前已经把紫色点像素画出来了,假如最近一个绘制的紫色像素坐标为(x,y),下一个绘制的像素是两个黄色二选一,坐标分别为(x+1,y+1)(x+1,y),那么如何来确定该绘制哪一个呢?考虑两个黄色像素坐标的中点(x+1,y+0.5)也就是图中黑圈的位置和直线的关系,可以得到如下结论:

  • 若中点在直线上方,则画(x+1,y)
  • 否则画(x+1,y+1)

如何判定一个点与直线的关系呢?我们已经知道直线的斜率公式为y=kx+b定义函数f(x,y)=kx-y+b考虑0<k<1的情况,当f(x,y)=0时点(x,y)在落在直线中,当f(x,y)>0时点在直线上方,f(x,y)<0时点在直线下方。在确定画(x+1,y)还是(x+1,y+1)时,我们把中点(x+1,y+0.5)代入f(x,y)中计算,根据结果进行选择即可。代码如下:

// 中点划线算法
    public void DrawLineMidline(Vector2 start, Vector2 end, Color color)
    {
        int startX = Mathf.RoundToInt(start.x);
        int endX = Mathf.RoundToInt(end.x);
        int startY = Mathf.RoundToInt(start.y);
        int endY = Mathf.RoundToInt(end.y);

        // 无法计算斜率k的情况
        if (startX == endX)
        {
            int x = Mathf.RoundToInt(start.x);
            for (int i = startY; i <= endY; i++)
            {
                screen.SetPixel(x, i, color);
            }
        }
        else
        {
            float k = (end.y - start.y) / (end.x - start.x);
            float b = end.y - k * end.x;

            if (0 < k && k < 1) // 只考虑这个斜率范围,其它情况修改参数可以很简单实现
            {
                screen.SetPixel(startX, startY, color);
                int y = startY;
                for (int i = startX; i < endX; i++)
                {
                    float value = this.GetLineValue(i, startY + 0.5f, k, b);
                    if (value < 0)
                    {
                        y = y + 1;                        
                    }
                    screen.SetPixel(i, Mathf.RoundToInt(y), color);
                }
            }
        }
    }

    private float GetLineValue(float x, float y, float k, float b)
    {
        return y - k * x + b;
    }

4. Bresenham 算法

中点划线算法中,频繁调用f(x,y)的计算,比较消耗。我们可以对直线对一般表达式f(x,y)进行一番推导,在0<k<1的斜率范围内,我们是从起点开始,对x递增计算对应的y坐标来确定要绘制的像素,将起点(x_0,y_0)和终点(x_1,y_1)带入,可以得到k=\frac {(y_1-y_0)}{(x_1-x_0)}b=y_1-x_1\frac{(y_1-y_0)}{(x_1-x_0)}
从斜率公式y=kx+b很容易得到直线的一般表示f(x,y)=kx-y+bkb带入上式,不难推导出f(x,y)=(y_0-y_1)x+(x_1-x_0)y+x_0y_1-x_1y_0
那么就可以得到f(x+1,y)=f(x,y)+(y_0-y_1) f(x+1,y+1)=f(x,y)+(y_0-y_1)+(x_1-x_0)算法只需要计算出f(x+1,y+0.5),后续不再需要乘法,只要加上相应对量就可以了,得到算法

public void DrawLineBresenham(Vector2 start, Vector2 end, Color color)
    {
        int startX = Mathf.RoundToInt(start.x);
        int endX = Mathf.RoundToInt(end.x);
        int startY = Mathf.RoundToInt(start.y);
        int endY = Mathf.RoundToInt(end.y);

        // 无法计算斜率k的情况
        if (startX == endX)
        {
            int x = Mathf.RoundToInt(start.x);
            for (int i = startY; i <= endY; i++)
            {
                screen.SetPixel(x, i, color);
            }
        }
        else
        {
            float k = (end.y - start.y) / (end.x - start.x);
            float b = end.y - k * end.x;

            if (0 < k && k < 1) // 只考虑这个斜率范围,其它情况修改参数可以很简单实现
            {
                screen.SetPixel(startX, startY, color);
                float d = GetLineValue(startX, startY + 0.5f, k, b);
                int y = startY;
                for (int i = startX; i < endX; i++)
                {
                    if (d < 0)
                    {
                        d += (endX - startX) + (startY - endY);
                        y = y + 1;
                    }
                    else
                    {
                        d += (startY - endY);
                    }
                    
                    screen.SetPixel(i, Mathf.RoundToInt(y), color);
                }
            }
        }
    }

相关文章

  • 图形学自问自答1——直线光栅化

    如何在屏幕上画线段 光栅化是一个连续数据离散化的过程,而最简单的一个连续数据离散化的过程是如何将一条线段在屏幕中画...

  • GAMES101图形学之光栅化(上)

    前提 可以先看这个 《计算机图形学基础》之图像的光栅化[https://www.jianshu.com/p/98d...

  • 图形学 光栅化详解(Rasterization)

    计算机的屏幕是二维的平面坐标,以左上角为原点,x轴向右增加,y轴向下增加。 在3D图形学中,物体是3维的,拥有X,...

  • 光栅图形学

    光栅显示器上显示的图形,称之为光栅图形。光栅显示器可以看作是一个象素矩阵,光栅显示器上显示的任何一个图形,实际上都...

  • 计算机图形学三:光栅化

    经过变换之后,不管是正交投影还是透视投影,都被变换成[-1,1]的立方体,接下来就是要绘制在屏幕上,叫做光栅化 1...

  • 光栅化理解

    什么叫光栅化? 光栅化( Rasterize/rasteriztion)官方翻译成栅格化或者像素化。没错,就是把矢...

  • 光栅化渲染管线详解

    光栅化渲染管线是学习图形学的基础,学习渲染管线流程时,如果对其中的各个关键步骤理解不够深入,可能会看得一头雾水。这...

  • 3D渲染-光线追踪-包围盒

    一、回顾 1、理解 上节通过光栅化和光线追踪的对比,引入光线追踪。 在光栅化中,其实就是构建网格,然后在像素网格中...

  • 光栅化

    计算机中表示图形有两种方式,一种是点阵表示,一种是顶点表示。 点阵表示是光栅显示系统显示时所需要的表示形式,光栅化...

  • GAMES101图形学之光栅化(下)

    前提 经过了 MVP 变化,和 是否在三角形内部测试 之后,随之出现的一个问题就是锯齿,也叫作走样,下面讨论如何反...

网友评论

      本文标题:图形学自问自答1——直线光栅化

      本文链接:https://www.haomeiwen.com/subject/segozktx.html