1. 程式人生 > >[AlgorithmStaff] Bresenham快速直線算法

[AlgorithmStaff] Bresenham快速直線算法

urn info csdn 設計 csharp hand 官方 ima mat

操作系統: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 都遞增 1y 則增加 1 或不增加。具體上圖,假設已經繪制到了 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 > 0) << 1) - 1;
    int uy = ((dy > 0) << 1) - 1;
    int x = x1, y = y1, eps;

    eps = 0; 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 << 1) >= 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 << 1) >= 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 / 2;
            int ystep = (y0 < y1) ? 1 : -1;
            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 < 0)
                {
                    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快速直線算法