前言
近段時(shí)間我在工作中實(shí)現(xiàn)了一個(gè)動(dòng)畫(huà)功能,其中涉及到動(dòng)畫(huà)元素要按一定的軌跡在屏幕上移動(dòng),運(yùn)動(dòng)軌跡的生成我使用了Path.cubicTo方法來(lái)實(shí)現(xiàn)的,這個(gè)方法其實(shí)是生成了一條有兩個(gè)控制點(diǎn)的貝塞爾曲線。借此機(jī)會(huì)我再收集了一些資料,想系統(tǒng)地學(xué)習(xí)一下貝塞爾曲線相關(guān)的知識(shí)。
簡(jiǎn)介
貝塞爾曲線(Bézier curve)又被稱(chēng)為貝茲曲線或貝濟(jì)埃曲線,是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學(xué)曲線,它的數(shù)學(xué)基礎(chǔ)是伯恩斯坦多項(xiàng)式(Bernstein polynomial,since 1912),1959年法國(guó)數(shù)學(xué)家Paul de Casteljau提出了數(shù)值穩(wěn)定的de Casteljau算法,開(kāi)始貝塞爾曲線的圖形化應(yīng)用研究,而貝塞爾曲線的名稱(chēng)來(lái)源于一位就職于雷諾的法國(guó)工程師Pierre Bézier,他在1962年開(kāi)始對(duì)貝塞爾曲線做了廣泛的宣傳,他使用這種只需要很少的控制點(diǎn)就能生成復(fù)雜平滑曲線的方法來(lái)進(jìn)行汽車(chē)車(chē)體的工業(yè)設(shè)計(jì)。
貝塞爾曲線因?yàn)樗刂坪?jiǎn)便卻具有極強(qiáng)的描述能力,迅速在工業(yè)設(shè)計(jì)和計(jì)算機(jī)圖形學(xué)等相關(guān)領(lǐng)域得到了廣泛應(yīng)用。比如在矢量繪圖中,貝塞爾曲線用來(lái)給需要無(wú)限制地縮放的平滑曲線定模,許多繪圖軟件都提供了繪制貝塞爾曲線的功能。貝塞爾曲線還用于動(dòng)畫(huà)時(shí)間控制以實(shí)現(xiàn)美觀逼真的緩動(dòng)效果,還用于機(jī)器人轉(zhuǎn)動(dòng)手臂等方面的設(shè)計(jì)。
de Casteljau算法原理
在向量AB上選擇一個(gè)點(diǎn)C,使得C分向量AB為u:1-u(即|AC|:|AB|=u)。給定點(diǎn)A、B的坐標(biāo)以及u(u∈[0,1])的值,點(diǎn)C的坐標(biāo)則為C=A+(B-A)*u=(1-u)*A+u*B。

貝塞爾曲線原理
利用de Casteljau算法可以計(jì)算貝塞爾曲線上的點(diǎn)C(u),u∈[0,1]。因此,通過(guò)給定一組u的值,即可算出貝塞爾曲線上的坐標(biāo)序列,從而繪制出貝塞爾曲線。
為了計(jì)算n階貝塞爾曲線上的點(diǎn)C(u),u∈[0,1],首先將控制點(diǎn)連接形成一條折線00-01-02······0(n-1)-0n,利用de Casteljau算法,計(jì)算出折線中每條線段0j-0(j+1)上的一個(gè)點(diǎn)1j,使得點(diǎn)1j分該線段的比為u:1-u,然后在折線10-11-12-······-1(n-1)上遞歸調(diào)用該算法,以此類(lèi)推。最終求得一個(gè)點(diǎn)n0,已證明點(diǎn)n0一定是曲線上的點(diǎn)。

上圖中u=0.4,這從幾何形狀上形象地說(shuō)明了de Casteljau算法的過(guò)程,可以將上述直觀的幾何描述表達(dá)為算術(shù)方法,如下圖所示:

首先將所有的控制點(diǎn)排成一列,如上圖最左列,每一對(duì)相鄰的控制點(diǎn)之間畫(huà)出兩個(gè)箭頭,分別指向右下方和右上方,在兩個(gè)箭頭交匯的位置記下一個(gè)新的點(diǎn)。比如控制點(diǎn)ij和i(j+1)生成新的控制點(diǎn)(i+1)j。指向右下方的箭頭表示乘以(1-u),指向右上方的箭頭表示乘以u(píng)。
將上述過(guò)程表達(dá)為下面的算法:
Input:arrayP[0:n] ofn+1 points and real numberuin [0,1]
Output:point on curve,C(u)
Working:point arrayQ[0:n]
fori:= 0 to n do
? ? Q[i] :=P[i]; // save input
fork:= 1 to n do
? ? fori:= 0ton - kdo
? ? ? ? Q[i] := (1 -u)Q[i] +uQ[i+ 1];
returnQ[0];
這個(gè)算法還可以被遞歸地表達(dá),上述點(diǎn)列還有其他有趣的性質(zhì),詳細(xì)描述請(qǐng)參考論文Finding a Point on a Bézier Curve: De Casteljau's Algorithm。
貝塞爾曲線展示
一階貝塞爾曲線

給定點(diǎn)P0和P1,一階貝塞爾曲線就是這兩點(diǎn)間的一條直線段,對(duì)應(yīng)公式:

二階貝塞爾曲線

給定點(diǎn)P0、P1和P2,二階貝塞爾曲線是由下述方程產(chǎn)生的軌跡

這個(gè)方程可以被描述為由兩段獨(dú)立的一階貝塞爾曲線再按de Casteljau算法合成的,分別是由 P0 至 P1 的連續(xù)點(diǎn) Q0描述一條線段,由 P1 至 P2 的連續(xù)點(diǎn) Q1描述另一條線段,最后由 Q0 至 Q1 的連續(xù)點(diǎn) B(t)描述了貝塞爾曲線。整理上述公式變成:

三階貝塞爾曲線

二維平面或者更高維度的空間中的4個(gè)點(diǎn)P0、P1、P2和P3可以定義一條三階的貝塞爾曲線,這條曲線從P0點(diǎn)開(kāi)始向P1點(diǎn)方向運(yùn)動(dòng),最后從來(lái)自P2點(diǎn)的方向運(yùn)動(dòng)到達(dá)P3點(diǎn)。一般點(diǎn)P1和P2不會(huì)在曲線上,它們用來(lái)控制曲線運(yùn)動(dòng)的方向;點(diǎn)P0與P1間的距離將會(huì)影響曲線在轉(zhuǎn)向P2之前向P1點(diǎn)方向運(yùn)動(dòng)的遠(yuǎn)近和快慢。按照二階貝塞爾曲線方程可以表達(dá)為一階的形式,三階貝塞爾曲線也可以由兩個(gè)二階貝塞爾曲線組合來(lái)線性地表達(dá):

將方程遞歸帶入整理得到:

更一般地,n階貝塞爾曲線的一般方程為:

例如,n=5時(shí),即5階貝塞爾曲線的方程為:

貝塞爾曲線應(yīng)用
Android SDK中提供的API
在android.graphics包中的Path類(lèi)提供了幾個(gè)可以繪制貝塞爾曲線的API,常用的就是lineTo、quadTo和cubicTo三個(gè)方法,分別可以繪制一、二、三階的貝塞爾曲線,一階沒(méi)什么可說(shuō)的,就是通常的畫(huà)直線,傳入的參數(shù)就是目標(biāo)點(diǎn)坐標(biāo)。下面分別是quadTo和cubicTo方法的接口說(shuō)明:


自定義貝塞爾工具
在實(shí)際項(xiàng)目中我自己寫(xiě)了一個(gè)貝塞爾估值器類(lèi),核心方法如下:

后記
我第一次寫(xiě)這樣長(zhǎng)篇的技術(shù)博客,慚愧??!想以此加深自己的理解,也方便以后迅速查閱相關(guān)的資料。寫(xiě)博客真的是一個(gè)深入學(xué)習(xí)的好方法,讓我不再流于解決問(wèn)題的表面,通過(guò)查找資料可以透過(guò)直接的技術(shù)方案發(fā)現(xiàn)方案背后的邏輯和原理。理解了最本質(zhì)的東西,其他的都是可以舉一反三、觸類(lèi)旁通的方法變幻。加油,少年!~
Thanks To:
Finding a Point on a Bézier Curve: De Casteljau's Algorithm