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

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

# 概要編：Dracoについて
![draco3d-vert-360x274.png](https://qiita-image-store.s3.amazonaws.com/0/58526/f6e139ec-3a6e-6786-5a73-7a8a1a4d07d9.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](https://qiita-image-store.s3.amazonaws.com/0/58526/d972a752-30a8-4c12-df6f-de79de6b7f98.png)
 *上記ブログURLより引用*

実際によく利用されるOBJフォーマットのデータサイズとDraco圧縮したサイズを比較しました。
テストデータはよく利用されるBunny.objです。
![bunny.png](https://qiita-image-store.s3.amazonaws.com/0/58526/7e77f31d-0aeb-5b65-b9e4-3f82d928d309.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圧縮なんて足元にも及びませんｗ


## 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にデコードされたバッファが入るようになっています。
<img width="308" alt="geodata.png" src="https://qiita-image-store.s3.amazonaws.com/0/58526/e607e27e-8f41-f350-662d-dbe19655492a.png">

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

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

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

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

描画するときの注意点ですが、Dracoはデータが圧縮されるので、かなりたくさんの三角形が扱えてしまいます。
そのため、インデックスバッファが１６ビット精度ではたりないので、３２ビットが必要になってきます。
    
    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の配列を作成するときに３２ビット整数の配列で取得しています。

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上に流れる３Ｄデータの容量を削減して、
快適なWebGLライフを送りましょう。

それではまた！