從零實現3D圖像引擎:(3)超級重要的2D矩形裁剪

1. 數學分析

爲什麼我們要畫2D直線,要做2D的直線-矩形裁剪?原因很簡單,無論遊戲世界是2D的還是3D的,最終都要投影到玩家的屏幕上,3D的東西最終要是要投影到視平面上。所以3D遊戲仍然有很多東西要在2D視平面上做。對於3D遊戲的裁剪就有兩種方法:一是在3D空間做裁剪,利用視域體的各個面與三角形中直線的關係來做,這叫做立方體空間裁剪;另一種是,把3D物體先投影在2D視平面上,然後在視平面上對2D直線做矩形裁剪,叫作圖像空間裁剪。三角形都是由直線組成的,屏幕是矩形的,所以用矩形裁剪一條直線是非常重要的,而且也不是想當然的簡單的,也有很多因素要考慮。

 

1) 直線被矩形裁剪的四種情況

直線被矩形裁剪只有以下4種情況,如圖:

裁剪情況

 

2) 裁剪的核心問題?

我們來看看,裁剪的核心問題是什麼:

裁剪的核心問題

如上圖,兩個端點分別爲p0,p1的直線線段,被某一矩形裁剪。裁剪的過程是給定輸入值x0,y0,x1,y1和矩形RECT,輸出被裁剪後的直線兩端點p0',p1'的座標x0',y0',x1',y1'。

 

通過觀察上圖,我們可以知道,我們其實就在求兩條直線的交點,一條是紅線,一條是矩形的某個邊。這讓我們想起了幾何學時的幾種直線表示形式,然後求交點就是解一個方程組的過程。這沒有錯,但是我們可以走捷徑,因爲矩形的某個邊不是水平線,就是垂直線,所以這第二條直線的表示非常簡單。垂直線是x = c,水平線是y = c。

 

我們來表示一下這條紅線。比如我們用點斜式:

斜率m = (y2 - y1) / (x2 - x1)

點斜式: y - y1 = m * (x - x1)

 

整理後,用y表示x:

x = (y - y1) / m + x1

也可以用x表示y:

y = m * (x - x1) + y1

 

所以與某個矩形的邊的交點,可以把那個邊線的表示形式直接代入,即可求得。

 

3) Cohen-Sutherland

上面我們解決了核心問題,那就是如何求交點。而Cohen-Sutherland算法爲我們解決了另一個文章開頭所說的問題,就是如何處理直線與矩形之間的4種關係。

 

該算法雖然簡單,但極其精妙,後面會講如何精妙!它把空間分成了各個部分,並賦予了相應的位代碼,所以只使用少量的if語句,並且還是位運算,根據直線的兩端點,即可判斷出是上面哪種關係,如圖:

Cohen-Sutherland

如果你想感動一下,那一定要非常仔細的研究這個圖了。

首先,我們看紅色代碼的部分,請不要看那個黑色的十進制code,中間裁剪矩形的代碼是0000。正左方、正右方、正下方、正上方分別是:

0001

0010

0100

1000

即,他們每個是其中的一個位標記,所以可以通過兩兩做或操作(|),來得到斜着的4個角的代碼(綠色)。

首先,根據這些特性,我們可以做一些工作了,我們定義p1code、p2code分別表示直線的兩個端點在這張圖的位置編碼,我們以p1code爲例,p2code相同,就不貼代碼了,下面一組簡單的判斷,就能求得恰當的p1code:

 

也許你覺得這沒有什麼,但神奇的是,通過現在的p1code和p2code的位編碼的組合,我們已經可以使用僅僅一個位運算操作來判斷線段是否完全被刪除,即求p1code & p2code的值。你可以嘗試一下,當點p1和點p2在同一側時,比如都在上方,無論是在左上、正上還是右上,這個表達式的結果都是1;而只要有一個點不在這一側,則這個表達式必然是0。所以可以只用一個位運算來過濾掉大量的裁剪操作。

 

還有一個表達式則很容易理解了:p1code == 0 && p2code == 0,當這個表達式爲true時,則直線完全被保留。

 

這時可能讀者會問了,還有一種完全刪除的情況並沒有被排除,那就是雖然兩個點在不同側,但是直線仍都在矩形的外面,應該也被全刪除。對於這種情況,沒有辦法,我們無法在現有的條件下來判斷了,在上面這兩個表達式過濾完大部分情況後,這種特殊情況只能在我們馬上要做的具體求點的過程裏面來判斷了。下面我們來做這件事。

 

對於直線的起始端點p0來說,在正方向和斜方向處理的複雜度不同,請看下圖:

Cohen-Sutherland求交點

請注意,我只畫了從p0發射的必然與相鄰的矩形邊緣有交點的情況,而所有沒畫其他情況,其實那些其他情況,大部分已經被上面所說的那兩個表達式排除了。還有一種情況是,比如p0在N這個位置,但是他與矩形上邊緣直線的交點在矩形的外面,對於這種情況,我們先把交點算出來,在計算完兩個端點分別的新值後再來判斷,這個後面會講。

 

對於在正方向(W、E、S、N),已經可以直接求出p0與矩形邊緣線的交點座標了,以N爲例:

上面的代碼求得了p1點的裁剪後的新點np1的座標(nx1, ny1)。這個就是利用上文的點斜式推得的公式計算的。可能你會發現有點地方不一樣,在求nx1的時候我們加了個0.5,這是因爲出現了除法,我們要把最後一步除法變成浮點數除法,然後通過+0.5,再捨棄小數位來達到四捨五入的目的。

 

然後就是斜方向的問題了。這個問題我們採取好理解的方式來處理,如圖p0nw。我們首先假定那根紅色的線是和矩形上方相交,我們使用正方向求解交點的方法,可以先把這個交點給求出來,求出來之後,判斷這個nx1,是不是在矩形上邊的範圍之內,如果在,那麼這個點就是對的,如果不在,那麼說明直線與左邊線相交,我們就需要再求直線與矩形左邊線的交點,而這個交點就是真正的np1了。讀者可以邊看着上圖中最左邊的那條紅線,邊看這段文字,非常好理解,以nw爲例,代碼如下:

 

聰明的讀者不難發現,其實對p2code的處理和對p1code的是一模一樣的。哈哈,因爲本來就和是哪個點沒關係,只和他們所在的區域可能與相鄰的哪些矩形邊有關係。

 

於是我們只剩下最後一個問題了,就是上文提到過的,可能直線的兩個點在不同側,但是與矩形沒交點的情況,我們只要把上面的結果做一下邊界檢查就可以找出這種情況了,代碼如下:

至此,大功告成,下面是完整的函數實現。

 

2. 代碼實現

 

 

3. 項目下載

這次的示例,是隨機生成直線,起點和端點的範圍是正負兩個屏幕的最大座標之間,裁剪區域是屏幕裏的一個矩形。每秒將500個這種直線通過矩形裁剪,如果與矩形有交點,則在矩形內畫出相應的線段。截圖如下:

矩形裁剪效果圖

 

項目完整代碼下載:>>點擊進入下載頁<< 

 

4. 補充內容

如果有時間,我會更新上Cyrus-Beck裁剪算法,如果您已實現,請留言分享,謝謝。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章