WebGL
glTF
draco
WebGLDay 12

劇的に3Dデータ容量を削減するDRACO圧縮のデータを自前描画する方法

はじめに

前半で、Draco圧縮の概要説明を行い、後半に実際にDraco圧縮したデータを自前描画でWebGLで表示してみます。

概要編:Dracoについて

draco3d-vert-360x274.png

GoogleがDracoというオープンソースの3Dデータ圧縮方式をご存知でしょうか?
https://google.github.io/draco/

Googleの発表時のブログ(https://opensource.googleblog.com/2017/01/introducing-draco-compression-for-3d.html) によると、既存のデータをZIP圧縮したものよりも、高い圧縮率を実現できるようです。

Draco1.png
上記ブログURLより引用

実際によく利用されるOBJフォーマットのデータサイズとDraco圧縮したサイズを比較しました。
テストデータはよく利用されるBunny.objです。
bunny.png
69451 Triangles

データ種別 データサイズ 圧縮率
オリジナルOBJ 4,930,097 Byte 100.0%
OBJデータをZIP圧縮 1,574,690 Byte 31.9%
Draco圧縮 108,926 Byte 2.2%

ZIP圧縮でデータサイズが1/3になりましたが、DRACO圧縮は1/45になりました。ZIP圧縮なんて足元にも及びませんw

glTFのDraco拡張について

一昨日のWebGLアドベントカレンダーでglTFについて解説されていましたね。
https://qiita.com/cx20/items/95127986f4b9fa91fe9b

glTFのデータフォーマットを策定しているクロノスグループでは、このdraco圧縮を取り込んで行こうとする動きがあります。
仕様はほぼ固まっており、glTF 2.0の KHR_draco_geometry_compression拡張として取り込んでいく方針のようです。
https://github.com/KhronosGroup/glTF/pull/874

glTFは特にWebGLで取り扱いしやすいフォーマットであるため、データ圧縮をすることで、
モデルデータを軽量にし、データ配信の容量をさげるという狙いがあります。
上記の圧縮率をみると、このフォーマットに対応しない手はないでしょう。

上記のbunnyのモデルデータで、5MB近くのデータをサイトで配信するとなると、モバイル環境では厳しいでしょう。
ZIP圧縮して1.5MBでも少し考えてしまう容量です。100KBバイトならどうでしょう?
恐らくモバイルへの配信を考えても問題なくデータとして組み込める容量だと思います。


実践編

では実際にデータを圧縮してWebGLで表示してみましょう。

Draco圧縮してみる

Dracoのリポジトリをとってきます。

$git clone https://github.com/google/draco

cmakeでビルドです。MacOSだと以下のような感じです。

$cd /path/to/draco/
$mkdir build
$cd build
$cmake ..
$make

ビルドできたでしょうか?Windowsの人うまくいきませんか?
Windows環境で、CMAKE_C_COMPILERがないとかエラーが出る人は、パスが通ってないです。
VisualStudio 2015でのビルドの場合は以下のような感じです。

 (VisualStudio2015 x64 NativeToolsコマンドプロンプトを起動する)
 ↓
 $cd /path/to/draco/
 $mkdir build
 $cd build
 $cmake -G "Visual Studio 14 Win64" -DCMAKE_C_COMPILER="C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/cl.exe" -DCMAKE_CXX_COMPILER="C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/cl.exe" ..

上記コマンドを実行すると、buildフォルダに VisualStudioのソリューション・プロジェクトファイルができているので、それを開いてビルドしてください。

さて、ビルドが完了したら、ビルドされたエンコーダツールで、試しにデータを作ってみましょう。
リポジトリにテストデータ入っているので、これを利用します。(自分の好きなデータを用意してください)

 $cd build
 $./draco_encoder -i ../testdata/bunny_norm.obj -o bunny.drc

 Encoder options:
   Compression level = 7
   Positions: Quantization = 14 bits
   Normals: Quantization = 10 bits

   Encoded mesh saved to bny.drc (94 ms to encode)

   Encoded size = 108926 bytes

   For better compression, increase the compression level '-cl' (up to 10).

はい。Dracoのデータができました。 -clでCompression levelが変更できますが、10とかにしても、あまり小さくはなりません。多少小さくはなりますが、OBJからの圧縮率としては誤差レベルでしょう。

WebGL表示編

Three.js使いの人

javascriptディレクトリにthree.jsを利用したサンプルが置いてあるので、そちらを参照してください。そのまま差し替えるだけで動きますね。
https://github.com/google/draco/tree/master/javascript/example

自前エンジンの人

それでは、上で圧縮したデータ(69451三角形のbunny.obj)を自前描画WebGLで表示してみましょう。

動作サンプル(iPhoneとかでも動きます)
https://kioku-systemk.github.io/dracoSample/

githubにサンプルソース一式を置きました。
https://github.com/kioku-systemk/dracoSample

順番に解説していきます。

圧縮したのC++のプログラムでしたが、ブラウザでデータをデコードするために、
JavaScriptのDracoのデコーダが用意されています。C++のソースコードをEmscriptenを利用して、jsに変換したものになります。
DracoRepository/javascript/draco_decoder.js がデコーダ本体です。
Draco_decoder.wasm はWebAssembly版です。今回はだいたいどこでも動くjs版を利用します。

Dracoのデコーダを読み込ませておきます。

 <script src="draco_decoder.js"></script>

デコーダはデコードしかしてくれないので、データのロードや、VertexBufferなどは
自分で作ってあげる必要があります。
今回は dracoloader.js というものを作成しました。 (https://github.com/kioku-systemk/dracoSample/blob/master/dracoloader.js)

 <script src="dracoloader.js"></script>

dracoloaderの loadDraco 関数でデータをロードしデコードします。
https://github.com/kioku-systemk/dracoSample/blob/5611528416d4e0afb10cbec52d70493602d8a552/dracoloader.js#L210

 loadDraco('bunny.drc', function (geodata) {
        console.log(geodata);
 });

デコードされたら、geodataにデコードされたバッファが入るようになっています。
geodata.png

とりあえず、決め打ちで入れていますが、いじりたい人はdracoloader.jsをいじりましょう。

Dracoのデータ形式ですが、EncodedGeometryTypeというものがあり、
TRIANGULAR_MESHPOINT_CLOUD の2種類のタイプがあります。
https://github.com/kioku-systemk/dracoSample/blob/5611528416d4e0afb10cbec52d70493602d8a552/dracoloader.js#L180

Dracoは、ポイントクラウドと呼ばれる点群と、三角形の幾何学形状をもつジオメトリデータを、効率よく圧縮できることを想定しているため、この2種類のデータ形式をサポートしています。それぞれのエンコードタイプにより、処理が異なるので注意して下さい。

頂点データおよび、インデックスデータを取得できたら、あとは自前描画のためにバッファを作成して、
描画してあげればOKです。

描画するときの注意点ですが、Dracoはデータが圧縮されるので、かなりたくさんの三角形が扱えてしまいます。
そのため、インデックスバッファが16ビット精度ではたりないので、32ビットが必要になってきます。

const stripsArray = new draco.DracoInt32Array();
const numStrips = decoder.GetTriangleStripsFromMesh(dracoGeometry, stripsArray);
indices = new Uint32Array(stripsArray.size());

https://github.com/kioku-systemk/dracoSample/blob/5611528416d4e0afb10cbec52d70493602d8a552/dracoloader.js#L70
Indexの配列を作成するときに32ビット整数の配列で取得しています。

WebGL 1.0では、標準では32ビット整数のインデックスバッファを利用できません。ローダで作ったバッファをそのまま使うには
拡張機能を有効にして、32ビット整数を利用できるようにしないといけません。

 gl.getExtension("OES_element_index_uint");

https://github.com/kioku-systemk/dracoSample/blob/5611528416d4e0afb10cbec52d70493602d8a552/vgl.js#L88

以上が、Dracoを自前描画するための解説です。

みなさんもDraco対応して、Web上に流れる3Dデータの容量を削減して、
快適なWebGLライフを送りましょう。

それではまた!