iT邦幫忙

2022 iThome 鐵人賽

DAY 22
1
Modern Web

30個遊戲程設的錦囊妙計系列 第 22

Trick 21: 如何畫出貝茲那曼妙的曲線

小哈多年前曾參與一個Adultswim上單車極速遊戲的製作,身為死神的主角,騎著單身,在看似優美實則險峻的跑道上飛馳,一邊在空中翻滾一邊對行人們揮動鐮刀。
Victorian BMX

Victorian BMX這個網頁遊戲因題材特殊也曾風靡一時,不過隨著Flash的告絕,現在已經無法在網頁上玩得到了。 Victorian BMX promo (2010)

關卡編輯器

在設計遊戲的過程中,為了製作關卡編輯器,對貝茲曲線進行了一些研究。今天分享這些相關的知識給大家,相信總有一天同學們能用得到。

貝茲曲線是什麼

貝茲曲線是1962年由法國工程師皮埃爾·貝茲(Pierre Bézier)發表,用來幫助車體的設計。曲線主要由兩個端點組成,然後中間再塞些控制點讓原本的直線彎曲。如果完全沒有控制點,那貝茲曲線就是直的。

至於中間控制點的數量,想要幾個就可以塞幾個,不過比較有意義且較常用的控制點個數是一個和兩個。
bezier curves

其中一個控制點的貝茲曲線是TrueType字型使用的繪圖法,而兩個控制點的貝茲曲線則是繪圖軟體中用來拉曲線的常見工具。用過Illustrator或Painter等繪圖軟體的同學們可能會覺得『上面這張圖和我平常看慣的拉線工具不一樣呀!』。我們需要先認識一下貝茲曲線的繪製原理,瞭解它的一些特性,然後才有辦法解開同學們的疑問。

零個控制點

零個控制點的貝茲曲線就是條直線,在曲線t%的位置,就是介於兩點之間靠終點t%的位置,等於(1-t%)的起點(P₀)和t%的終點(P₁)加起來的結果。
https://chart.googleapis.com/chart?cht=tx&chl=P_%7Bt%7D%20%3D%20(%201%20-%20t%25%20)%20P_%7B0%7D%20%2B%20t%25%20P_%7B1%7D
這條線就是由t%=0%,一直到t%=100%所有點的集合。

一個控制點

bezier calculation
要計算擁有一個控制點的貝茲曲線在t%的位置,可以先把三個點連成上圖藍色的虛線,然後以t%分別對兩段虛線以零個控制點的計算方法,得到兩個綠色的點,隨後再取綠色兩點連線在t%位置的橘點,這個橘點就是曲線在t%的位置。

計算流程的第一步是將兩個綠點以零個控制點的公式算出來:
(第一個綠色點)https://chart.googleapis.com/chart?cht=tx&chl=G_%7B1%7D%20%3D%20(1-t)P_%7B0%7D%20%2B%20tP_%7B1%7D
(第二個綠色點)https://chart.googleapis.com/chart?cht=tx&chl=G_%7B2%7D%20%3D%20(1-t)P_%7B1%7D%20%2B%20tP_%7B2%7D
然後同樣以零個控制點的公式,算出兩個綠點之間的最後結果:
(最後結果的點)https://chart.googleapis.com/chart?cht=tx&chl=P_%7Bt%7D%20%3D%20(1-t)G_%7B1%7D%20%2B%20tG_%7B2%7D
以上的公式聯立後,經過代入合併等計算,就能推導出一個較為簡化的公式:
https://chart.googleapis.com/chart?cht=tx&chl=P_{t}%20%3D%20P_%7B1%7D%20%2B%20(1%20-%20t%5E2)(P_%7B0%7D%20-%20P_%7B1%7D)%20%2B%20t%5E2(P_%7B2%7D%20-%20P_%7B1%7D)

計算曲線上0%到100%的所有橘點,把它們都連起來就是貝茲曲線的樣子。

兩個個控制點

bezier calculation bezier calculation2
兩個控制點的貝茲曲線,和上述的方法一樣,先把四個點連成藍色虛線,然後以t%分別對三段虛線以零個控制點的計算方法,得到三個綠色的點,這三個綠色點就形成了一個起點、一個終點和一個控制點的貝茲模型,如此一來我們就把兩個控制點降維成一個控制點。接著再使用上面「一個控制點」一模一樣的方法降維成零個控制點,最後就能算出曲線上t%的位置在哪兒了。

整個計算流程簡直就是遞迴函式最好的教材,是吧!不過用遞迴來計算比較花時間,一般在遊戲中是把整個計算過程用到的數學式全部聯立在一起,預先導出一個合併的數學式來用。

零個控制點 https://chart.googleapis.com/chart?cht=tx&chl=B(t)%20%3D%20(1-t)P_%7B0%7D%2BtP_%7B1%7D
一個控制點 https://chart.googleapis.com/chart?cht=tx&chl=B(t)%20%3D%20P_%7B1%7D%20%2B%20(1%20-%20t%5E2)(P_%7B0%7D%20-%20P_%7B1%7D)%20%2B%20t%5E2(P_%7B2%7D%20-%20P_%7B1%7D)
兩個控制點 https://chart.googleapis.com/chart?cht=tx&chl=B(t)%20%3D%20(1%20-%20t%5E3)P_%7B0%7D%20%2B%203(1-t)%5E%7B2%7DtP_%7B1%7D%20%2B%203(1-t)t%5E%7B2%7DP_%7B2%7D%20%2B%20t%5E%7B3%7DP_%7B3%7D

貝茲曲線的特性

  1. 起點和相鄰的控制點,決定了曲線在起點的斜率。
  2. 終點和相鄰的控制點,決定了曲線在終點的斜率。
  3. t%在曲線上代表的意義是時間,而曲線在t%位置上的斜率代表的是速度。
  4. 由3.可知,曲線上等距t%的位置集合,在空間上並不是等距的。

第四點的意思是,絕大部分的情況下,曲線在10%到20%之間的長度,並不等於20%到30%之間的長度。
要在電腦上以數學取得貝茲曲線上等距點的集合很沒有效率,所以通常是用等距t%去取得對應的點集合,然後再用線性的方法去求得近似等距的點集合。這個主題比較複雜,以後有機會再來詳談。

/** 寫一個函式取得貝茲曲線上某個點的位置(兩個控制點專用)
 * points: 四個點,分別是起點/第一控制點/第二控制點/終點
 * t: 一個介於0到1的數字,代表曲線上某個百分比的位置
 */
function getCubicBezierPoint(points: Point[], t: number): Point {
    // 預先算好1-t
    let 1_t = 1 - t;
    // 套用上面兩個控制點的數學式
    let x = 1_t * 1_t * 1_t * points[0].x
          + 3 * 1_t * 1_t * t * points[1].x
          + 3 * 1_t * t * t * points[2].x
          + t * t * t * points[3].x;
    // 為了同學們能看清楚才這樣寫的
    // 同學在寫自己的函式庫時,應該要想辦法優化這段程式    
    let y = 1_t * 1_t * 1_t * points[0].y
          + 3 * 1_t * 1_t * t * points[1].y
          + 3 * 1_t * t * t * points[2].y
          + t * t * t * points[3].y;
    // 回傳結果
    return new Point(x, y);
}

在電腦上畫貝茲曲線,就是利用上面這個函式,把曲線上t=0%、t=1%、t=2%...一直到t=100%的點都找出來,然後將這些點連在一起。如果遊戲中的曲線不需要太精細的解析度,那麼取較為少量的點來畫也行。

繪圖軟體的曲線控制桿

在繪圖軟體裏,一條彎彎曲曲的線其實是一串頭尾相連的貝茲曲線,每一小段貝茲曲線都各有兩個控制點,而且繪圖軟體一般只會畫出與起點或終點相連的線段,因為這讓它們看起來像控制桿。
bezier control bars
如果我們在繪圖軟體中強迫前一段終點的斜率相等於下一段起點的斜率,那麼我們等於一次控制一個點左邊和右邊的控制桿,如此便能確保一段貝茲曲線和下一段曲線能平滑地連在一起。

CG示範專案
今天的示範專案因為要支援不同控制點的個數,所以程式內是使用遞迴的方法繪製曲線。


上一篇
Trick 20: 把鎧甲拉到身上裝備的拖曳控制器
下一篇
Trick 22: 遊戲的正義由數字保安來維護
系列文
30個遊戲程設的錦囊妙計32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言