如果曾經試著使用電腦進行繪圖,應該對貝茲曲線(Bézier curves)不陌生,它是以法國工程師Pierre Bézier來命名,結合程式設計,只要幾個控制點,就能夠建立圓滑曲線。

貝茲曲線既是數學也是程式,試著自行實作並進一步擴展到貝茲曲面,對於數學或者是程式面來說,都是很棒的探索過程。

貝茲曲線的數學

想要在電腦上進行曲線繪製,方法之一是構造數學函數,運用電腦算出每個點的位置,然後畫出曲線。但是,有時候因為數學不夠好,或者是難以找出數學公式來表現出想要曲線,怎麼辦呢?貝茲曲線本質上也是一種函數,只是形式固定,卻可以用來近似出各種曲線,近似的原理簡單來說,仍是使用直線。

用直線來近似曲線?感覺不可思議?想想看,給你兩個點,可以畫出一條直線,若在這兩點中間再取一個點並移動,就可以畫出折線,在兩點之間取得點越多,並適當地將這些點移至想要的曲線之路徑上,畫出來的折線就會像是個曲線了,兩點之間被取得的點稱之為節點(Knot)。

當然,若要自行移動兩點之間的每個節點,是一件很費力的事,最好是有個函數,給定兩個或者多個控制點(Control point),就能自動計算出中間必要的節點,這樣的函數稱為樣條函數(Spline function),而貝茲曲線是大多數人最為熟知的一種樣條函數。

最簡單的樣條函數是使用兩個控制點,在這兩點中間取許多節點,然後連接起來,當然結果還是直線,只不過是由數條直線組成罷了。如果有三個控制點P0、P1、P2,在P0與P1間直線的四分之一處,找個點Q0,在P1與P2間直線的四分之一處,找個點Q1,這時,Q0與Q1會構成一條直線,在此同時,我們也在Q0與Q1之間直線的四分之一處找個點K0,這就找到了一個節點,當切分點改為四分之二、四分之三就又可以找到K1、K2兩個節點,將P0、K0、K1、K2、P2連起來,就會像是個曲線。

因此,若切分時是1/n,像是1/10甚至是1/100,就人類肉眼看來就會是圓滑的曲線了,這方式就是二次(Quadratic)貝茲曲線。如果有四個控制點P0、P1、P2、P3,那麼由P0、P1、P2可以得到Kn個點,P1、P2、P3可以得到Ln個點,將Kn與Ln的點兩兩對應、畫出直線,就可以在這些直線上取得n-2個節點,P0與n-2個節點及P3,就可以畫出三次(Cubic)貝茲曲線,三次貝茲曲線是最常用的曲線,然而,依上方式繼續擴展,也可以求得四次、五次或更高階的貝茲曲線。

貝茲曲線的程式

若有0到n-1個控制點Pi,線性、二次、三次貝茲曲線的公式分別會是:

線性:P0 + (P1-P2) * t
一次:P0 * (1-t)^2 + 2 * P1 * t(1-t) + P2 * t^2
三次:P0 * (1-t)^3 + 3 * P1 * t(1-t)^2 + 3 * P2 * t^2(1-t) + P3 * t^3

很顯然地,這是一個二項式展開,而係數方面為[1, 1]、[1, 2, 1]、[1, 3, 3, 1],也就是C(n, k),可以用巴斯卡三角形來記憶。

巴斯卡三角形對程式人來說,是個常見的程式演算,寫來不難,由於很少會使用四次或以上的貝茲曲線,因此,就求係數來說,可以使用以下的程式碼,只有在大於3時,才會遞迴求解:

def combi(n, k):
p = ;
if n < 4:
return p[n][k];
return 1 if k == 0 else combi(n, k - 1) * (n - k + 1) // k

雖然三次貝茲曲線是最常用的,然而,想要實作出可應付n次的貝茲曲線,由於公式單純只是二項式展開,也只要遞迴就可以了。在三維空間中,若使用直角座標,那麼,點會有x、y、z三個值,只要分別針對這三個值來運算,就可以求得節點座標,而這種演算方式還有個很酷的名字,稱為de Casteljau's演算。

雖然貝茲曲線是以Pierre Bézier之名來命名,然而,他並不是首位發明者,最初由Paul de Casteljau於1959年運用de Casteljau演算法開發;另一個有趣的事實,對前端來說也許不陌生,CSS中有個cubic-bezier()函式,可以用來建立動畫之類的過渡效果。

從曲線到曲面

之所以會想要探討貝茲曲線,一開始是想要能在OpenSCAD中,描述一些有著複雜曲線的模型。OpenSCAD基本上是個簡易的語言,連內建的模組與函式都非常少,像貝茲曲線這種東西,自然必須要想辦法自行實作出來。

不過,就算描述出貝茲曲線了,還是有個難處,必須要能在三維空間中畫線,但OpenSCAD沒有內建這個模組,這是因為在幾何上,線是沒有粗細的,然而,在3D列印上,沒有粗細的線,也就無法列印而沒有意義。

當線有了粗細時,就算只是實作兩點間的直線,也有著許多考量,簡單來說,程式碼實作比較複雜,最好是隱藏起來成為一個模組,然後,基於兩點之間的直線模組,再實現出可連接多個點的折線模組。

在能夠繪製出貝茲曲線之後,自然會想要挑戰貝茲曲面,不過,在這之前先想到的是,要怎麼畫得出曲面?類似於曲線可由許多小直線來構成,曲面其實也可以由許多小平面來構成,只是這些小平面是什麼形狀呢?三角形!因為三點一定共面,這也就是為什麼在崎嶇不平的地面上,三腳椅會很好用的原因,如果想要描述的面不是三角形,就想要辦法將這些面分解為許多的小三角形。

一旦有了相關的函式與模組之後,以它們為基礎,才能將焦點集中在描述曲茲曲面上。令人意外地,相對於程式實作上的繁複,曲茲曲面在數學上的概念上簡單多了。

如果有16個控制點,每四個點用來畫一條三次貝茲曲線,這樣就有了四條貝茲曲線,現在於這四條曲線各取一個節點,用這四個節點就可以構成另一方向的貝茲曲線。一開始的四條貝茲曲線上,各有n個節點的話,就可以構成n條不同方向的貝茲曲線;若n夠大,這n條橫向的貝茲曲線看來就像是曲面。

當然,實際上要的是曲面而不是多條曲線,因此,這n條貝茲曲線上計算出來的各個點,再用來切割出許多的三角面,拼起來後就是貝茲曲面了,若對程式實作上的許多細節有興趣的話,可以看看〈貝茲曲面〉(https://goo.gl/6ZT8yC)的介紹。

數學與程式彼此驗證

從我首次實作出貝茲曲線開始,到實作出貝茲曲面又過了快半年,這中間其實還試過許多不同的方式,不過都失敗了,一方面也是因為我看不太懂貝茲曲面的數學描述,對定義根本不清,題目當然就做不出來,只好將這題目丟到腦袋的背景執行緒中,等待哪天又靈光一現。

不過,等待也不光只是等待。在這中間我做的事情,就是像方才所談到的,試著將目前已實現的成果重構、反覆思考,以抽取出基礎的部份,慢慢地,少了那些實作細節的紛擾,看著後續嘗試的程式碼,通往靈感的路線就逐漸看得清晰了,也終於能夠理解貝茲曲面的數學描述是怎麼一回事。

這就是為什麼我會持續創作一些作品,記錄想法,並且試著做出dotSCAD程式庫(https://goo.gl/Yb2IOR)的原因了。

如果有興趣,可以看看我的程式庫原始碼,以及提交歷史,其中,就有著許多我的作品與文件中想法的影子,以及我所體驗過的,這數學與程式彼此驗證的有趣過程。

作者簡介


Advertisement

更多 iThome相關內容