SONICMOOV Googleページ

続:WebGLでベジェ曲線を描いてみた

続:WebGLでベジェ曲線を描いてみた

  • このエントリーをはてなブックマークに追加

12月になり、各所でアドベントカレンダーが始まっていますね。ソニックムーブのスタッフブログも25日まで毎日更新となります。

ソニックムーブ Advent Calendar 2014 開催です!

 

初日を担当します、フロントエンジニアのらくさんです。元々3日目を担当する予定だったので初日らしい内容というわけでもなく、WebGLでベジェ曲線を描いてみた の続きになります。前回、2次ベジェ曲線の断片ひとつをシェーダで描画することはできました。今回は、複数の2次ベジェ曲線で構成されるパスの内部を塗りつぶします。

ステンシルバッファを使って凹型ポリゴンを塗りつぶす

本題に入る前に、ステンシルバッファを使って凹型ポリゴンを塗りつぶす方法を紹介します。次節では、これを2次ベジェ曲線のパスを塗りつぶすのに応用します。

fig3fig4

上図左の凹型ポリゴンABCDEを塗りつぶす場合を考えます。まず、このポリゴン上の頂点のうちどれか一つを選びます。ここでは点Aを選ぶものとします。次に、凹型ポリゴン上の隣り合った2つの頂点と点Aが作る三角形を全て作ります。この場合、ABC, ACD, ADE の3つが作られます。これらの三角形を全て塗ったとき、奇数回塗られた領域は凹型ポリゴンの内側、偶数回塗られた(または一度も塗られなかった)領域は外側となります。

この方法はステンシルバッファを使うと簡単に実装できます。はじめにステンシルバッファを0でクリアし、ステンシル関数(stencilFunc)を ALWAYS、ステンシル操作(stencilOp)を INVERT にして三角形 ABC, ACD, ADE を描画します。すると「奇数回塗られた領域」だけがステンシルバッファ上で0でない状態になります。次に、ステンシル関数 NOTEQUAL で凹型ポリゴン全体を覆う形状(バウンディングボックスや凸包等)を描画すれば、ステンシルバッファでマスクされた結果がカラーバッファに描画されます。

ステンシルバッファにABC, ACD, ADEを描画:

gl.clear(gl.STENCIL_BUFFER_BIT | gl.COLOR_BUFFER_BIT);

gl.stencilFunc(gl.ALWAYS, 0, ~0);
gl.stencilOp(gl.KEEP, gl.INVERT, gl.INVERT);
gl.colorMask(false, false, false, false);
gl.drawArrays(gl.TRIANGLES, ...);  // ABC, ACD, ADEを描画

ステンシルバッファでマスクしてカラーバッファに描画:

gl.stencilFunc(gl.NOTEQUAL, 0, ~0);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
gl.colorMask(true, true, true, true);
gl.drawArrays(gl.TRIANGLE_FAN, ...);  // 凸包を描画

デモを実行する

ここで紹介した方法は OpenGLプログラミングガイド 14章「様々なテクニック」の「ステンシルバッファを用いた、塗りつぶされた凹型ポリゴンの描画」に記載されています。詳しくはそちらをご覧ください。

2次ベジェ曲線への応用

fig5afig5b

上図左の2次ベジェ曲線(赤い線)の内側を塗りつぶすことを考えます。まず、この2次ベジェ曲線のアンカーポイント(黒い点)だけを直線で繋いでポリゴンを作ります。そして、その内側(上図左でグレーの領域)を前述の方法でステンシルバッファに書き込みます。次に、2次ベジェ曲線によって作られる領域をステンシルバッファに加えたり削ったりします。上図右の青いところが加える領域、緑のところが削る領域です。

デモを実行する

このデモのソースを見ていただくとわかると思いますが、ポリゴン部分の描画と2次ベジェ曲線によって作られる領域の加えたり削ったりは、一回の描画処理にまとめてしまうことができます。ポリゴン部分の頂点全てに、2次ベジェ曲線の塗りつぶされる側となる頂点属性、例えば (0.5,0.5) を渡せば、ポリゴン部分の処理も2次ベジェ曲線のシェーダでできてしまいます。

アンチエイリアス

前節のデモでは、塗りつぶした領域の境界部分にジャギーが発生しています。これを滑らかにするには、境界近傍のフラグメントには境界線までの距離によって0〜1のアルファ値が書き込まれるようにします。

アンチエイリアス対応フラグメントシェーダ:

#extension GL_OES_standard_derivatives : enable
precision mediump float;
varying vec2 p;
void main(void) {
    vec2 px = dFdx(p);
    vec2 py = dFdy(p);
    float fx = (2.0*p.x)*px.x - px.y;
    float fy = (2.0*p.x)*py.x - py.y;
    float sd = (p.x*p.x - p.y)/sqrt(fx*fx + fy*fy);
    float alpha = 0.5 - sd;
    if (alpha > 1.0) {
        // inside
        gl_FragColor = vec4(1.0);
    } else if (alpha < 0.0) {
        // outside
        discard;
    } else {
        // near boundary
        gl_FragColor = vec4(alpha);
    }
}

これは、前回の参考文献に挙げた GPU Gems 3 : Chapter 25. Rendering Vector Art on the GPU にあるHLSLのソースコードをGLSLに書き換えたものになります。詳細はそちらをご覧ください。

このシェーダでは dFdx, dFdy という関数を使用していますが、これは OES_standard_derivatives という拡張機能になります。これを使用するために、次のコードを実行して拡張機能を有効にします。

if (!gl.getExtension("OES_standard_derivatives")) {
    alert("no extension: OES_standard_derivatives");

また、フラグメントに書き込んだアルファ値をMSAAの遮蔽率として解釈させるために SAMPLE_ALPHA_TO_COVERAGE を有効にします。

gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);

デモを実行する

(しかし、iOS8のWebGLはアンチエイリアスが効かないようです…)

WebGLでフォントを描画してみる

最後に、WebGLで2次ベジェ曲線を描画する応用例としてWebGLでフォントを描画してみます。

デモを実行する

このデモは、freetypeをEmscriptenでJavaScriptに変換して使用しています。この記事では2次ベジェ曲線しか扱っていないため、このデモはTrueTypeベースのフォント(NotoSerif-Bold.ttf)を使用しています。PostScriptベースのフォントを描画するには3次ベジェ曲線に対応する必要があります。

(このデモはかなりやっつけで書いたので、ソースが汚い&たぶん色々おかしいところがあると思います…)

  • このエントリーをはてなブックマークに追加

記事作成者の紹介

らくさん(フロントエンドエンジニア)

フロントエンドエンジニアのらくさんです。JswfPlayerという弊社サービスでFlash再生エンジンを開発したり、動画広告Guileの動画プレーヤーを開発したりしています。

フロントエンドエンジニア募集中!

×

SNSでも情報配信中!ぜひご登録ください。

×

SNSでも
情報配信中!
SONICMOOV Facebookページ SONICMOOV Twitter SONICMOOV Googleページ
フロントエンドエンジニア募集中!

新着の記事

mautic is open source marketing automation