WebGLでベジェ曲線を描いてみた
フロントエンジニアのらくさんです。iPhone6はSIMロックフリー版にしました。iPhone6をしばらく使ってからiPhone5sに戻ると、とても小さく感じますね。でも、iPhone6だと片手でQWERTYキーボード使おうとすると、Aを押そうとしてもSになってしまいます。もうちょっとだけ小さくして欲しかった…
さて、iOS8からMobileSafariでもWebGLが使えるようになりました。皆さんWebGLやってますか? WebGLというと3Dグラフィックスという印象が強いですが、3D以外にも描画に関するさまざまな処理を行うことができます。この記事では、そんなWebGLで「ベジェ曲線を描画するシェーダ」を作ってみます。
ベジェ曲線はCanvas 2Dの方を使えば簡単に描けますが、WebGLだけでベジェ曲線を描けると便利なこともあります。また、これからWebGLも含めたOpenGL系の技術をを学ぼうという人にとっては「シェーダってのはこんなこともできるんだ」という参考になるかと思います。
なお、この記事では2次ベジェ曲線のみを扱います。3次ベジェ曲線については次に挙げた文献を参照してください。
参考文献
この記事は、次の文献を参考にしています(英文)。
GPU Gems 3 : Chapter 25. Rendering Vector Art on the GPU
また、この文献の日本語訳が販売されているようです。少々お高いですが…
GPU Gems 3 日本語版
2次ベジェ曲線の式
開始点 Ps(xs,ys)
制御点 Pc(xc,yc)
終了点 Pe(xe,ye)
とすると、2次ベジェ曲線上の任意の点 P(x,y)
は、媒介変数 t
を使って次のように表されます。t
を 0
から 1
まで変化されると開始点から終了点までの2次ベジェ曲線が得られます。
P = t2Pe + 2t(1-t)Pc + (1-t)2Ps
x
と y
にわけて書くと次のようになります。
x = t2xe + 2t(1-t)xc + (1-t)2xs
y = t2ye + 2t(1-t)yc + (1-t)2ys
2次ベジェ曲線は放物線
次に
開始点 (0,0)
制御点 (0.5,0)
終了点 (1,1)
のベジェ曲線を考えます。これらを前述の式に代入すると、
x = t2 + t(1-t) = t
y = t2
となります。この2式から t
を消すと、
y = x2
となり、この2次ベジェ曲線は y = x2
という放物線であることが分かります。
したがって、この3点が作る2次ベジェ曲線を描くには、この3点が作る三角形の内部で x2-y=0
となる(実際には x2-y
が「ほぼ0
」となる)ピクセルだけ塗ればよくて、曲線の内側(または外側)を塗りつぶすには x2-y
の正負によって塗り分ければよいことになります。
では (0,0),(0.5,0),(1,1)
とは異なる3点が作る2次ベジェ曲線はどうでしょうか。ここでは数学的な詳細は省きますが、放物線を傾けたり剪断変形したりすることで、どんな2次ベジェ曲線も作り出すことができるので、2次ベジェ曲線は放物線である、と言えます。
2次ベジェ曲線シェーダ
シェーダを自分である程度書いたことのある人なら、ここまで来ればピンときたことでしょう。
任意の3点が作る三角形の各頂点に対して頂点属性 (0,0),(0.5,0),(1,1)
を与え、シェーダの補完機能で得られた値を x2-y
の式で評価してやるだけで、2次ベジェ曲線が描けてしまいます! そしてこれはとても単純な処理のため、高速に動作します。
シェーダのソースコードは次のようになります。
頂点シェーダ:
1 2 3 4 5 6 7 8 |
[sourcecode lang="C"]precision mediump float; attribute vec2 vertex; attribute vec2 attrib; varying vec2 p; void main(void) { gl_Position = vec4(vertex, 0.0, 1.0); p = attrib; }[/sourcecode] |
フラグメントシェーダ:
1 2 3 4 5 6 7 8 9 |
[sourcecode lang="C"]precision mediump float; varying vec2 p; void main(void) { if (p.x*p.x - p.y > 0.0) { gl_FragColor = vec4(0.6, 0.6, 0.6, 1.0); } else { gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0); } }[/sourcecode] |
頂点シェーダの vertex
には三角形の頂点座標を与えます。attrib
には、三角形の頂点毎に (0,0),(0.5,0),(1,1)
のいずれかを与えます。attrib
の値がフラグメントシェーダに渡される際、三角形の形状に合わせて適切に補完されます。フラグメントシェーダの4行目で、ベジェ曲線の内側か外側かの判定をしています。
これで2次ベジェ曲線の断片ひとつをシェーダで描画することができました。
まとめと次回予告
WebGL(やOpenGLなど)でベジェ曲線を描きたい場合、まず思いつくのが曲線を小さな線分に分割して近似することです。しかし、それで滑らかな曲線を描画するには分割数を十分多くしなければならず、頂点数がかなり増えてしまいます。
一方この記事では、2次ベジェ曲線の特徴をよく理解しシェーダの機能にうまく当てはめることで、分割せずに直接描画しました。しかし、複数の2次ベジェ曲線(と直線)で構成されるパスを描画できなければ実用にはなりません。それについては別の記事にわけて書きます(ソニックムーブ Advent Calendar 2014 の3日目に書く予定です)。