webgl

この記事はWebGL Advent Calendar 2015、19日目の記事です。


WebGLでVTFでポリゴン表示した


普段ネイティブOpenGLをちょいちょい使っているJavascripterですが、webglはスルーしてました。
理由としては、自分が扱いたいデータは、中~大規模なジオメトリキャッシュデータで、
まだwebglでは無理かな・・と思っていたためです。
しかし、そろそろwebglの時代が来るんじゃないか!? もう既に来てるんじゃないか!? (物理的に)
という気がしましたので、重い腰を上げてやってみました。
自作数学ライブラリをC++から移植して、カメラも移植して、グリッドも出して
という感じにwebgl初心者なりに最速の方法で、、

WebGLでMP4やPNGデータをソースとして、VTFでポリゴン表示しました


PNGデータ(R8G8B8)をソースとして表示した例


↑ Mikuさん1人 = PNG画像1枚です。 

(もともとウェイトや物理の割り当てが適当な部分あります..腕の後ろのへんのポリゴンウェイト塗り忘れですorz

MP4データ(H.264/YUV444)をソースとして表示した例


本当は、可逆mp4(H.264/RGB、qp=0)でストリーミング再生したかったんですが
可逆mp4はchromeでGLのテクスチャとして読めなかったです。

やったこと


やってることとしては、MikuMikuDanceからのデータをMMDBridge(後述)を使用して
1フレームごとに、PNGデータにベイクして吐き出し、VTFでデコードしてます。
つまり、このPNGデータがMikuさんのポリゴンです。

1フレーム分のデータ。



200フレーム出力すると、こんな感じです。


通常、頂点インデックスを使用する人が多いと思いますが、
煩雑で面倒なため、全て三角形を展開して、フラットにしてベイクしています。
また、今回出力してるのは、頂点配列、法線配列のみです。

1枚を拡大


VTF (Vertex Texture Fetch)


VTFや、その他の部分でもかなり wgld.org のサイトにお世話になりました。
特にVTFのサンプルはかなり何度も見させていただきました。ありがとうございます。

wgld.org - 頂点テクスチャフェッチ(VTF)

VTFについては、特に特別なことはしてなくて、上記サイトと同様に、floatのvboを作って
indexを送り込んでいます。描画部分のコードは、以下のような感じでごく普通です。
マテリアルは、上手く行けば使おうと思っていたのですが、ギリギリまで粘って何とか間に合った感じなので、現状1マテリアル固定です。

gl.bindBuffer(gl.ARRAY_BUFFER, this.index_vbo);
for (i = 0; i < this.material_list.length; i = i + 1) {
        material = this.material_list[i];
        index_count = material.polygon_count() * 3;
        camera.draw(shader);
        material.draw(shader);

        gl.drawArrays(gl.TRIANGLES, index_offset, index_count);
        index_offset = index_offset + index_count;
}
gl.bindBuffer(gl.ARRAY_BUFFER, null);

float to RGB / RGBA


これがキモになる部分かと思うんですが、既に他の方がカレンダーで議論されているので素人のぼくが出る幕はないですが、
実験した結果だと、今のところfloat to RGBはかなり厳しくて、RGBA無いと見るに耐えないと言う感じです。
最終的にこちらにある、encode32 と decode32 の実装を使わさせていただきました。

stackoverflow - How do I convert a vec4 rgba value to a float?


上記のを使っていたんですが、下駄を履かせて全て正の数にした上で正規化すればRGBでもいい感じになることが分かりました。
最終的に

// エンコード(C++)
// valueはあらかじめ頂点の最小/最大値を元に正規化した上で、16777215.0f(24bitだから)をかけたものを入れている。
static int float2color(float value) {
        int b = floor(value / 256.0 / 256.0);
        int g = floor((value - b * 256.0 * 256.0) / 256.0);
        int r = floor(value - b * 256.0 * 256.0 - g * 256.0);
        return ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}

// デコード(GLSL)
// 返値を取得した後、エンコード時に行った正規化や16777215.0fの乗算を元に戻している。
highp float color2float(vec3 color) {
        color = color * 256.0;
        return (color.r + color.g * 256.0 + color.b * 256.0 * 256.0);
}

で上手く行きました。

下駄及び正規化するときの幅は、欲を出すなら毎フレーム計算する必要がありますが、このような大きな範囲で動かない
ちょっとしたダンス程度であれば、全フレームの全頂点のうち最小/最大のものを採用すれば良さそうです。
今回はそのようにしています。


なぜPNGやMP4にしたか?


世の中、流行ってるものが正になるものです。
動画や画像をアップロードできるサービスは、くさるほどありますが、
ポリゴンをアップロードできるサービスは、そんなに無いものです。
みんなが使うデータ形式は、どんどん最適化されていきます。
HTML5のvideoなんて勝手にキャッシュしてくれるみたいですし。乗れるのもなら乗るしかないです!

事前調査として、

Computer Graphics Gems JP 2012

の「Chapter 9 Kinectで取得したアニメーションデータに対して、動画圧縮技術を使って圧縮を行う 」 を読みました。
そこで、 MPEG-4 Face and Body Animation (FBA) という形式があることが分かりました。
世の中、こんな変なこと考えてる人いるもんですね!
しかしFBAはボーンデータなどに特化したもので、ジオメトリキャッシュには使えないようでした。


次に、メッシュの圧縮について調べていたら、
webgl-loader

というのを発見しました。
こちらは、色々な符号化をJavascriptでがんばって、何とか小さくできました、的なものでした。
モデルデータの、頂点インデックスなどもそうですが、がんばって小難しく小さくするというのは、
大抵使いにくく、融通が効かないものです。
特に独自フォーマットは使いたくなかったので、あまり参考になりませんでした。

そういえば、昔、PNGの拡張ヘッダに3Dモデルのバイナリを詰め込んで
掲示板にアップロードするとPNG画像としてサムネイルがでる3Dモデル形式とかも、あったような気がします。
変なことしようとしてるので、こういった変なことやってる人たちは大変貴重ですね。


ジオメトリキャッシュとは

実際出したかったのはメッシュ1つではなくて、ジオメトリキャッシュです。
ジオメトリキャッシュとは、シミュレーション結果をベイクするのに良く使われるような形式のもので、
1つのデータに複数フレームの情報が全部ベイクされて入っています。
フォーマットとしては、Alembic、MDD、PC2、とかあるらしいですが、Alembic以外良く知らないです。
ちなみにFBXにもMayaのキャッシュデータ入るみたいですね。


MMDBridgeとは

私がたまに開発している、MikuMikuDanceからAlembicや、毎フレームOBJなりなんなりを書き出すためのプラグインです。
物理演算やIKによって変形したメッシュを、全フレームベイクすることが出来ます。

使用例としては、有名どころでは、このあたり
で使用されています。

最終目標としては、AlembicをWebGLで再生したかったんですが、
ネイティブの壁が高すぎてローカルでnode.jsプラグインとかローカル通信とかじゃないと無理かなぁって気がしてます。
興味ある方はこちら見てみてください。MMDBridgeのWebGLエクスポータ誰か書いてくれないかなぁ(チラッチラッ


明日のWebGL Advent Calendar 2015は、yuichiroharai さんです。
Comments