3Dモデル表現形式glTFについて

# はじめに

3Dモデルの表現形式は非常に種類が多く、
使用できるアプリ、できること、できないことをはっきりさせておかないと大変です。
この記事では最近出てきた表現のglTFについて紹介します。

# glTFとは

https://github.com/KhronosGroup/glTF

- 3Dモデルを配布するためのフォーマットで、主にjsonで記述される．拡張子glb(バイナリ形式)/gltf(分割形式)。
- 分割形式では頂点などのデータは分離してバイナリに格納される。
- 当然、ボーンやテクスチャも表現できる。
- アニメーションも保存できる。

## glTFファイルの表示・検証

KhronosGroupが用意しているツールを使って表示・検証ができます。

表示
https://github.khronos.org/glTF-Sample-Viewer-Release/

検証
https://github.khronos.org/glTF-Validator/


# COLLADAと比較してみた

colladaから変換する方法がいくつかありますが、今回はblenderから同じ立体を出力して比較してみます。

## 条件

_※以下は古い情報です。Blender4.xではglTFは標準でバイナリ/分割の両方の形式でエクスポートできます。_

- Ubuntu 16.04 64bit
- Blender 2.79b (公式サイトよりDL)
- Blender-Exporter from github
  - https://github.com/KhronosGroup/glTF-Blender-Exporter
- Blender起動時の状態からカメラとランプを削除し、cubeだけexportしてみる。

## ファイル形式、容量

生成されるファイルはcolladaがdae単体、gltfの分割形式が.gltfと.binの2つです。
容量については以下の通りで、colladaのほうが当然ながら大きくなります。

```
gltf_collada$ ls -lh ./
合計 12K
-rw-rw-r-- 1 h-yaguchi h-yaguchi  612  4月 17 22:13 default_cube.bin
-rw-rw-r-- 1 h-yaguchi h-yaguchi 4.0K  4月 17 22:13 default_cube.dae
-rw-rw-r-- 1 h-yaguchi h-yaguchi 2.7K  4月 17 22:13 default_cube.gltf
```

## マテリアルの記述

colladaではマテリアルの記述はかなり複雑で、

`library_effects`に詳細を記述し、

```
  <library_effects>
    <effect id="Material-effect">
      <profile_COMMON>
        <technique sid="common">
          <phong>
            <emission>
              <color sid="emission">0 0 0 1</color>
            </emission>
            <ambient>
              <color sid="ambient">0 0 0 1</color>
            </ambient>
            <diffuse>
              <color sid="diffuse">0.64 0.64 0.64 1</color>
            </diffuse>
            <specular>
              <color sid="specular">0.5 0.5 0.5 1</color>
            </specular>
            <shininess>
              <float sid="shininess">50</float>
            </shininess>
            <index_of_refraction>
              <float sid="index_of_refraction">1</float>
            </index_of_refraction>
          </phong>
        </technique>
      </profile_COMMON>
    </effect>
  </library_effects>
```

`library_material`でeffectを指定し、

```
  <library_materials>
    <material id="Material-material" name="Material">
      <instance_effect url="#Material-effect"/>
    </material>
  </library_materials>
```

形状情報を書くところで指定し、

```
       <triangles material="Material-material" count="12">
```

さらに`visual_scene`で`bind_material`します。

```
          <bind_material>
            <technique_common>
              <instance_material symbol="Material-material" target="#Material-material"/>
            </technique_common>
          </bind_material>
```

gltfは`material`に記述して`mesh`で指定します。

```
    "materials" : [
        {
            "name" : "Material", 
            "pbrMetallicRoughness" : {
                "baseColorFactor" : [
                    0.6400000190734865, 
                    0.6400000190734865, 
                    0.6400000190734865, 
                    1.0
                ], 
                "metallicFactor" : 0.0
            }
        }
    ], 
    "meshes" : [
        {
            "name" : "Cube", 
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 2, 
                        "POSITION" : 1
                    }, 
                    "indices" : 0, 
                    "material" : 0
                }
            ]
        }
    ], 
```

## データ格納

colladaはxmlにベタ書きです。
頂点の例を見てみましょう。

```
        <source id="Cube-mesh-positions">
          <float_array id="Cube-mesh-positions-array" count="24">1 1 -1 1 -1 -1 -1 -0.9999998 -1 -0.9999997 1 -1 1 0.9999995 1 0.9999994 -1.000001 1 -1 -0.9999997 1 -1 1 1</float_array>
          <technique_common>
            <accessor source="#Cube-mesh-positions-array" count="8" stride="3">
              <param name="X" type="float"/>
              <param name="Y" type="float"/>
              <param name="Z" type="float"/>
            </accessor>
          </technique_common>
        </source>
```

一方、gltfの場合はバイナリ形式で保存されており、アクセス方法をjsonで指定します。

```
    "accessors" : [
        {
            "bufferView" : 0, 
            "componentType" : 5121, 
            "count" : 36, 
            "max" : [
                23
            ], 
            "min" : [
                0
            ], 
            "type" : "SCALAR"
        }, 
        {
            "bufferView" : 1, 
            "componentType" : 5126, 
            "count" : 24, 
            "max" : [
                1.0000004768371582, 
                1.0, 
                1.0000005960464478
            ], 
            "min" : [
                -1.0000003576278687, 
                -1.0, 
                -1.0000003576278687
            ], 
            "type" : "VEC3"
        }, 
        {
            "bufferView" : 2, 
            "componentType" : 5126, 
            "count" : 24, 
            "max" : [
                1.0, 
                1.0, 
                1.0
            ], 
            "min" : [
                -1.0, 
                -1.0, 
                -1.0
            ], 
            "type" : "VEC3"
        }
    ], 
```

これのバッファへの格納法は次のように書かれているようです。

```
    "bufferViews" : [
        {
            "buffer" : 0, 
            "byteLength" : 36, 
            "byteOffset" : 0, 
            "target" : 34963
        }, 
        {
            "buffer" : 0, 
            "byteLength" : 288, 
            "byteOffset" : 36, 
            "target" : 34962
        }, 
        {
            "buffer" : 0, 
            "byteLength" : 288, 
            "byteOffset" : 324, 
            "target" : 34962
        }
    ], 
    "buffers" : [
        {
            "byteLength" : 612, 
            "uri" : "default_cube.bin"
        }
    ], 
```

.binファイルもわけられそうな雰囲気ですね。

ここから立体を出力しているのは

```
    "meshes" : [
        {
            "name" : "Cube", 
            "primitives" : [
                {
                    "attributes" : {
                        "NORMAL" : 2, 
                        "POSITION" : 1
                    }, 
                    "indices" : 0, 
                    "material" : 0
                }
            ]
        }
    ], 
```

`bufferView`のインデックスでしょうか。

ここで、気になるところがありますね。
バイナリファイルにアクセスするには格納方法がもっと詳細に記述されていないといけません。
具体的には、何バイトの、符号ありなしの、浮動小数点型もしくは整数型がわからなければ読めません。

それらしきものは、`componentType`ですが、`5121`と`5126`とあります。
これだけだとわからないので困ったら公式を読みましょう。

https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors

`5121 = UNSIGNED BYTE`,`5126 = FLOAT`と出てきました。

これはOpenGLの定数をそのまま使っているようです。

参考: https://javagl.github.io/GLConstantsTranslator/GLConstantsTranslator.html

# おわりに

COLLADAとの比較を通して見ましたが、
glTFはjsonで書かれている分すっきりしていて、表現の仕方もスマートだと感じました。

~~一方で、実用上はソフトの対応状況がネックになります。~~
~~現時点(2018.4)のblender最新版では、別途プラグインをインストールする必要がありました。~~
Blender4.xでは標準で対応しています。