にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

ベジェ曲線を手で描いてみる

ベジェ曲線は、ベクタ形式の画像で曲線を表現するのに使われています。フォントのアウトラインもベジェ曲線の集まりです。ベジェ曲線で表現されるのは一つの曲線の区間だけです。大抵は複数のベジェ曲線が集まって一つの図形を表現します。

ベジェ曲線には制御点の個数によって2次ベジェ、3次ベジェなどが存在します。グラフィックソフトで使われるのは2次, 3次がほとんどです。

ドローソフト等を扱っていると慣れ親しむことになるベジェ曲線ですが、その線がどのように計算されるかについては感覚的にしかとらえられていない場合がほとんどなのではないでしょうか。ここでは、2次、3次のベジェ曲線を手で描いてみようと思います。自分で描いてみることで、その動作に理解を深めることができるかもしれません。ベジェ曲線上の点を求める方法は、ド・カステリョ(De Casteljau)のアルゴリズムを元にしています。

2次ベジェ

f:id:nixeneko:20150626074354p:plain
まずは2次のベジェ曲線を考えます。2次のベジェ曲線には3つの制御点が存在します。
曲線の両端の制御点から、真ん中の制御点に近づくような曲線になります。
2次ベジェ曲線は TrueType フォントのアウトラインに使われています*1

描き方

まず、適当に制御点3点を決めてプロットします。
3つの点を順に  \{P_0, P_1, P_2\} とします。
f:id:nixeneko:20150626053012j:plain

 P_0\mbox{-}P_1,\ P_1\mbox{-}P_2 間をそれぞれ結びます。
f:id:nixeneko:20150626053023j:plain

2等分

線分  P_0P_1, P_1P_2 をそれぞれ2等分した点を結びます。
f:id:nixeneko:20150626053033j:plain

結んだ線を2等分する点が求めるベジェ曲線上の点になります。
f:id:nixeneko:20150626053055j:plain

点を求めるのに使った補助線を消すと次のようになります。
f:id:nixeneko:20150626053117j:plain

4等分

次に、線分  P_0P_1, P_1P_2 をそれぞれ4等分します。
f:id:nixeneko:20150626053138j:plain

線分  P_0P_1, P_1P_2 をそれぞれ 1:3 に内分する点を結びます。
f:id:nixeneko:20150626053150j:plain

結んだ線分を 1:3 に内分する点を求めます。この点が求めるベジェ曲線上の点になります。
f:id:nixeneko:20150626053230j:plain

補助線を消すと
f:id:nixeneko:20150626053331j:plain

同様に線分  P_0P_1, P_1P_2 をそれぞれ 3:1 に内分する点を結び、結んだ線分をさらに 3:1 で内分した点を求めます。
f:id:nixeneko:20150626053412j:plain

補助線を消すと
f:id:nixeneko:20150626053428j:plain

8等分

次に、同様に線分  P_0P_1, P_1P_2 をそれぞれ8等分した点を追加します。
f:id:nixeneko:20150626053503j:plain

線分  P_0P_1, P_1P_2 をそれぞれ 1:7 に内分する点を結び、結んだ線分をさらに 1:7 で内分した点を求めます。
f:id:nixeneko:20150626053527j:plain

以下同様に 3:5 で内分する場合
f:id:nixeneko:20150626053547j:plain

5:3 の場合
f:id:nixeneko:20150626053605p:plain

7:1 の場合
f:id:nixeneko:20150626053621j:plain

補助線を消すと
f:id:nixeneko:20150626053643j:plain
だんだん形が見えてきました。

16等分

次に、同様に線分  P_0P_1, P_1P_2 をそれぞれ16等分する点を追加します。
f:id:nixeneko:20150626053721j:plain

同様にベジェ曲線上の点を求めていきます。
1:15 の場合
f:id:nixeneko:20150626053759j:plain

3:13 の場合
f:id:nixeneko:20150626053815j:plain

5:11 の場合
f:id:nixeneko:20150626053844j:plain

7:9 の場合
f:id:nixeneko:20150626053912j:plain

9:7 の場合
f:id:nixeneko:20150626053928j:plain

11:5 の場合
f:id:nixeneko:20150626053945j:plain

13:3 の場合
f:id:nixeneko:20150626054001j:plain

15:1 の場合
f:id:nixeneko:20150626054019j:plain

補助線を消すと
f:id:nixeneko:20150626054044j:plain

求めた点を滑らかに結ぶと、だいたい求めるベジェ曲線になります。
f:id:nixeneko:20150626054125j:plain


また、ここではベジェ曲線上の点を求める際に引いた補助線を毎回消していましたが、補助線をすべて残したままにすると次のようになります。
2次ベジェ曲線は、引いた全ての補助線の内側に接するような曲線になります。

8等分
f:id:nixeneko:20150626054306j:plain
16等分
f:id:nixeneko:20150626054323j:plain

一般化: パラメータ t

さて、一般化します。
ある実数  t \  (0 \leq t \leq 1) について、線分  P_0P_1, P_1P_2 をそれぞれ  t:(1-t) に内分する点を  P'_0, P'_1 とします。

次に、線分  P'_0P'_1 を結びます。線分  P'_0P'_1 t:(1-t) に内分する点  P''_0 が求めるベジェ曲線上の点になります。
f:id:nixeneko:20150626062033p:plain
制御点列  \{P_0, P_1, P_2\} で規定される2次ベジェ曲線は、2組の制御点列
 \{P_0, P'_0, P''_0\}, \  \{P''_0, P'_1, P_2\} で規定される2つの2次ベジェ曲線に分割することができます。この分割手法をド・カステリョ(De Casteljau)のアルゴリズムといいます。


ここで、  t の値を  t = 0 から  t=1 まで動かすと、点  P''_0 の軌跡はベジェ曲線になります。実際に  t の値を  t = 0 から  t=1 まで動かした時の点  P''_0 の軌跡を描いたGIF動画を次に示します。  t は曲線上の点の位置を指定し、パラメータと呼ばれます。
f:id:nixeneko:20150626072516g:plain


また、この  P''_0 の座標  \boldsymbol{p}(t) は、次の式によって与えられます。ただし  \boldsymbol{p}_0, \boldsymbol{p}_1, \boldsymbol{p}_2 は制御点の座標を示すベクタです。
 \displaystyle
\boldsymbol{p}(t) = \boldsymbol{p}_0(1-t)^2 + \boldsymbol{p}_12t(1-t) + \boldsymbol{p}_2t^2



3次ベジェ

f:id:nixeneko:20150624061448p:plain
3次ベジェは4つの制御点からなります。両端の点の間に2つの制御点がはさまる感じです。
Illustratorや、PostScriptフォントのアウトライン表現で使われています。

次の4つの制御点 \{P_0, P_1, P_2, P_3\}で定められるベジェを描いてみようと思います。
f:id:nixeneko:20150624061354p:plain
これは、実際にイラストレータで制御点をこの位置に指定すると次のような曲線になります。
f:id:nixeneko:20150624061448p:plain

では、次から実際に描いてみましょう。次からベジェ曲線上の点を求めていきますが、ある程度の個数求まったらなめらかな線で結ぶと概形が得られます。

半分

まず、制御点  P_0\mbox{-}P_1,\  P_1\mbox{-}P_2,\ P_2\mbox{-}P_3 を結びます。
f:id:nixeneko:20150624061714p:plain

線分  P_0P_1, P_1P_2, P_2P_3 をそれぞれ二等分します。
f:id:nixeneko:20150624062028p:plain

二等分した点を次図のように結びます。
f:id:nixeneko:20150624062204p:plain

2等分した点を結んだ線分2つについて、その2等分点同士を結びます。
f:id:nixeneko:20150624062516p:plain

結んだ線分を2等分する点が、求める3次ベジェ曲線上の点になります。
f:id:nixeneko:20150624062650p:plain

引いた補助線を消すと
f:id:nixeneko:20150624062909p:plain
これで一点を求めることができました。

4等分

次にすすみます。
線分  P_0P_1, P_1P_2, P_2P_3 をそれぞれ4等分する点を追加します。
f:id:nixeneko:20150624063241p:plain

それらの線分を1:3に内分する点を次のように結びます。
f:id:nixeneko:20150624063502p:plain

さらに、先の図で結んだ線分2つについて、それぞれを 1:3 で内分する点を結びます。
f:id:nixeneko:20150624063735p:plain

結んだ線分を 1:3 に内分する点が求めるベジェ曲線上の点になります。
f:id:nixeneko:20150624063907p:plain

補助線を消すと
f:id:nixeneko:20150624063944p:plain

次の点も同様に描きます。ただし今度は線分を 3:1 で内分することになります。
f:id:nixeneko:20150625223154p:plain

補助線を消すと
f:id:nixeneko:20150624064632p:plain

8等分

次も同様です。
線分  P_0P_1, P_1P_2, P_2P_3 をそれぞれ8等分する点を追加します。
f:id:nixeneko:20150624065312p:plain

順にベジェ曲線上の点を求めていきます。
1:7で内分するとき
f:id:nixeneko:20150625223547p:plain

3:5のとき
f:id:nixeneko:20150625223756p:plain

5:3のとき
f:id:nixeneko:20150625223956p:plain

7:1のとき
f:id:nixeneko:20150625224339p:plain

だんだんと曲線の形が見えてきました。
f:id:nixeneko:20150624070858p:plain

16等分

次も同様に線分  P_0P_1, P_1P_2, P_2P_3 をそれぞれ16等分する点を追加します。
f:id:nixeneko:20150624072456p:plain

また同様にベジェ曲線上の点を求めていきます。
1:15
f:id:nixeneko:20150624072905p:plain

3:13
f:id:nixeneko:20150624073207p:plain

5:11
f:id:nixeneko:20150624073401p:plain

7:9
f:id:nixeneko:20150624073607p:plain

9:7
f:id:nixeneko:20150624073734p:plain

11:5
f:id:nixeneko:20150624073932p:plain

13:3
f:id:nixeneko:20150624074126p:plain

15:1
f:id:nixeneko:20150624074437p:plain

結果、この様になります。
f:id:nixeneko:20150624074528p:plain

32等分

32等分点についても対応する点を求めると次のようになります。
f:id:nixeneko:20150624074617p:plain

求めた点をイラストレーターによるベジェ曲線と重ねてみると、これらの点がベジェ曲線上にあることがわかります。
f:id:nixeneko:20150624074724p:plain

また、点を求めるのに使用した全ての補助線を表示させると次のようになります。
f:id:nixeneko:20150624074806p:plain

これをみると、求める3次ベジェ曲線のおおよその形が見えます。それに加え、  \{P_0, P_1, P_2\} および  \{P_1, P_2, P_3\} によって定義される2次ベジェ曲線の形が見えます。これは、  P_0P_1, P_1P_2, P_2P_3 の内分点同士を結んだ線を内分した点を求めることが、2次ベジェ上の点を求める方法と同一だからです。

一般化: パラメータ t

さて、一般化します。
パラメータとなる実数  t\  (0 \leq t \leq 1) を考えます。
ある  t について、線分  P_0P_1, P_1P_2, P_2P_3 をそれぞれ  t:(1-t) に内分する点を  P'_0, P'_1, P'_2 とします。

次に、線分  P'_0P'_1, P'_1P'_2 を結びます。線分  P'_0P'_1, P'_1P'_2 をそれぞれ  t:(1-t) に内分する点を  P''_0, P''_1 とします。

線分  P''_0P''_1 を結びます。線分  P''_0P''_1 t:(1-t) に内分する点  P'''_0 が求めるベジェ曲線上の点になります。
f:id:nixeneko:20150625232304p:plain

ここで、  t の値を  t = 0 から  t=1 まで動かし、点  P'''_0 の軌跡を描いたGIF動画を次に示します。
f:id:nixeneko:20150626025314g:plain


この  P'''_0 の座標  \boldsymbol{p}(t) は、次の式で与えられます。ただし  \boldsymbol{p}_0, \boldsymbol{p}_1, \boldsymbol{p}_2, \boldsymbol{p}_3 は制御点の座標を示すベクタです。
 \displaystyle
\boldsymbol{p}(t) = \boldsymbol{p}_0(1-t)^3 + \boldsymbol{p}_13t(1-t)^2 + \boldsymbol{p}_23t^2(1-t) + \boldsymbol{p}_3t^3

N次ベジェ

N次のベジェ曲線はN+1個の制御点の列  \{P_0, P_1, P_2, \cdots, P_N\} で定義されます。
2次, 3次の時と同様に、隣り合う制御点を結ぶ線分を内分して点を求め、それを点が一つになるまで繰り返していくという操作によりベジェ曲線上の点を求めることができます。
また、パラメータ  t (0 \leq t \leq 1) に対応するベジェ曲線上の点  \boldsymbol{P}(t) は次式で定義されます。

 \displaystyle
 \boldsymbol{P}(t) = \sum_{i=0}^{n}\boldsymbol{P}_iB_i^n(t)

ただし、  B_i^n(t) は次の式で定義され、n次のバーンスタイン基底関数とよばれます。

 \displaystyle
 B_i^n(t) = {}_n\mathrm{C} _i  t^i(1-t)^{n-i}=\frac{n!}{i!(n-i)!}t^i(1-t)^{n-i}

他の方法

ド・カステリョのアルゴリズムを使って1つのベジェ曲線を2つのベジェ曲線に分割することができるので、それを利用して曲線を再帰的に分割していって求める曲線を計算するということもできるのですが、実際に手描きで分割して曲線上の点を求めていくとすぐに線が込み入ってよく分からなくなる感じがあります。

参考

  1. コンピュータグラフィックス編集委員会『コンピュータグラフィックス』, CG-ARTS協会, 2004
    • 最近新版が出ました。そちらをおすすめします。

*1:TrueTypeフォントは2次B-スプラインらしいですが、TrueTypeフォントの制御点列で連続するoff-curve点の中点にon-curve点を挿入すると、2次ベジェ曲線の集まりと考えられます。