SIGGRAPH に来てますが、今出てるセッションがあんまり面白くなかったので暇つぶしにブログを書いてみるなど。

さて、OpenGL ES 3.0 も発表されたことですが、まだしばらくは WebGL も ES2.0 の世界が続きますよね。今回某ライブラリを WebGL に突っ込んでみたわけですが、その成果を発表するのはまたの機会にするとして、その過程で得られた WebGL 関連の細かなテク等をまとめておきたいと思います。

まず、OpenGL と同様に WebGL、ES にも EXT があります。iPhone/iPad などでは EXT Viewer とかのアプリもあるのでざっと眺めてもらってもいいかもしれません。

いろいろあるんですが、今のところ安心して使えそうなのはたいした数がありません。

http://www.khronos.org/registry/webgl/extensions/

ここで一番大事なのが、OES_texture_float です。このエクステンションで float フォーマットのテクスチャが使え、かつ render_to_texture で float バッファに書き込むのもうまくいきます。多くの GPGPU 的な処理を WebGL でやるときはこれを中心に使っていくことになると思います。

・・・というか、ES2.0 では 32bit integer の頂点アトリビュートやテクスチャがありません。あとたぶん整数テクスチャもないと思います。従って大きな数を扱うときは float を使わざるを得ない、ということになります。

エクステンションの使い方ですが、ここは JavaScript ならではの簡単な方法が用意されています。glew とかはいりません。最初に


gl.getExtension('OES_texture_float')
を呼ぶだけで、以降普通に使えるようになります。

今は WebGL でテセレーションをするのが目的なのですが(そうなんです)、ES 2.0 にも 3.0 にもジオメトリシェーダやテセレーションシェーダはないので、別の方法を考えることになります。頂点シェーダでテセレーションする方法は有名なのがありますが (Uniform で座標を送ってインスタンスレンダリングする系のやつ)、ここではもうちょっと普通の方法に挑戦してみたいと思います。Transform Feedback を render to texture でエミュレーションします。

ES 2.0 にはフレームバッファオブジェクトにテクスチャを割り当てる機能があるので、これと上記のエクステンションを使って、Subdivide した頂点座標を RGB にして書き込むことでテセレーション可能です。

頂点シェーダでこんな関数を用意しておきます。


      vec2 getUV(float index, float d) {
        float u = fract(index*d+0.5*d);
        float v = (floor(index*d+0.5*d)+0.5)*d;
        return vec2(u, v);
      }

      vec3 getPosition(float vertexID) {
         return texture2D(texPosition, getUV(vertexID, dim)).xyz;
      }

これで、頂点 ID を渡すと2Dテクスチャをルックアップして座標を返してくれるわけです。dim には1/正方テクスチャの幅(1ピクセルあたりのuvピッチ)を渡します。

計算した結果を書き込むときは、同じようにして ID から UV に変換します。


      void setPosition(float vertexID, vec3 p) {
        float u = fract(vertexID*dim)*2.0-1.0;
        float v = floor(vertexID*dim)*dim*2.0-1.0;
         gl_Position = vec4(u+0.5*dim, v+0.5*dim, 0, 1);
         color = p;
      }

フラグメントシェーダでは受け取った color をそのまま書けば、テクスチャになった頂点バッファに出力完了です。Blend を使えば加算とかもうまくできるかもしれませんが、あんまり試していません。

レンダリングフェーズではバーテックスシェーダで getPosition 関数でテクスチャを引きながらレンダリングしていけます。glVertexID はないので、GL_POINTS で頂点アレイに頂点番号を float で渡してやる、という感じになるかと思います。

ES には texture buffer も 1d texture もないので、普通の 2D texture を使ってインデクスからUVに変換しながら参照・書き込みする必要はあります。面倒ですが、C言語の2次元配列のように普通に作れば普通にできます。ただし、ここでクロスブラウザで正しく動かすための Tips がいくつかあります。

  1. float texture で 0-1 以外の値が正しく使えるのは、GL_RGB と GL_RGBA だけだと思った方がいい windows/mac の chrome/firefox では GL_LUMINANCE などでも1 以上の値が扱えましたが、linux の chrome、mac の Safari ではクランプされてしまいました。RGB だと大丈夫なようです。
  2. フィルタは NEAREST で使うが、ちゃんとピクセルセンターに合わせた方がいい(0.5pixel 足す)。ピクセル境界の NEAREST サンプルは実装によってまちまち。これは texturebuffer が実装されるまでは仕方なさそうです。
  3. render to texture したバッファをすぐに参照する場合は、やっぱりなんかしら同期しないとだめ。 glFlush や glBindFramebuffer でいけてるようですが、正しい解かどうかわかりません。Safari だとシステムごと死んだりします。どうしましょう。

次回はもっとコードと実例を交えながらもう少し詳しく説明していきたいと思います。