glTFローダー自作のコツと、要らない子になったglTF1.1


　この記事は WebGL Advent Calendar 2016 の７日目の記事です。
　
　2017/05/14追記
　（…ってことで当時登録しておいたのはいいんですが、内容に満足がいかなかったので、とりあえず限定公開の状態のまま放置を続けてしまい、
　結局そのまま2017年5月14日の今日まで放っておいてしまいました。ずっと放置するのも無責任なので、完成度的には不足・未完成ながらも、公開します）
　
# はじめに

　さて、[昨日（６日目）](http://qiita.com/emadurandal/items/1a034c4addd7ff8b5184)は、glTFの概要について説明しました。
　今日は、自前でglTFローダーを書く際のコツや、近々登場するglTFバージョン1.1についてお話ししようと思います。
　
　っと思って記事を書いていたのですが、なんと [Githubイシューのコメント](https://github.com/KhronosGroup/glTF/issues/817#issuecomment-274285706) によると、glTF1.1は欠番になってしまうようです。
　つまり、glTF1.0の次はglTF2.0が来ることになります。あーれーw

　2017/05/14追記
　（glTF2.0は2017年春に正式リリースという予定ですが、まだリリースされていません。どうやら仕様を盛り込みすぎてスケジュールが延びているようです。さすがに2017年内には出るでしょうが…）
　
　まぁ、それが良いでしょうね。glTF1.1は仕様に細かい調整が入っただけで、ユーザーにとって嬉しいめぼしい機能追加はあまりありません。1.0と1.1が乱立して互換性問題を招くよりは、次のバージョンはユーザーにとって意味のある機能がたくさん入ったメジャーバージョンアップの方が意味があると思います。
　
　とはいえ、せっかく記事は途中まで書いちゃったので、敢えて残すことにしますw
　
　以降の文章は、「glTF1.1はもう欠番になった」ことを念頭において、「こんなことがあったんだな」って感じで参考程度に読んでください ^o^/

# glTFローダーを自作する際のコツ

(WIP)

## 複数ファイルの読み込みをどうするか？

(WIP)

# glTFバージョン1.1は1.0とどう違うの？

　基本、あまり変わりません（何）　　　　いや、ほんとです。
　
　<del>glTF 1.1は、来年2017年の春の正式リリースに向けて、調整が進められています。</del>←前述の通り、欠番になりました。
　
　PBR（物理ベースマテリアル）対応などが予定されている更に将来のバージョン（glTF Nextと呼ばれています）などと違い、1.1は1.0策定後、「ああ、あそこの細かい仕様、今思えばこうしといた方がよかったなー」というような部分を、修正していった感じの小粒の変更内容となります。

　明確にうれしい機能追加があるわけではないので、<del>正直このバージョン1.1、普及するのか果たして……って感じではあります。「苦労して1.0対応したし、さらに1.1対応とか面倒臭いし、あまり変わってないなら別に要らなくね？」と各ライブラリや周辺ツールの開発者たちが考えてしまうと、普及するのは大変かもしれません。</del>←前述の通り、欠番に（以下略）

　でもそこは頑張ってほしい、というのがglTF公式の願いでしょうし、実際、関係各所には頑張ってほしいものです。
　ちなみに私の自作ライブラリ「GLBoost」はすでにglTF1.1対応済みです。日本国内におけるglTF警察 捜査一課の捜査官としては当然のことです。Yay！

　さて、ではバージョン1.1での具体的な変更点を挙げてみましょう。

## 一部、JSONのフォーマットが変わった

はい。何点か変わりました。でもそこまで大きいものではありません。主に２点です。

### `animation.sampler`が直接`accessor`を指定するようになった

1.0ではこうだったのが...

```
    "animations": {
        "animation_0": {
            "channels": [
                {
                    "sampler": "animation_0_translation_sampler",
                    "target": {
                        "id": "_pPyramid1",
                        "path": "translation"
                    }
                }
            ],
            "parameters": {
                "TIME": "animAccessor_0",
                "translation": "animAccessor_1"
            },
            "samplers": {
                "animation_0_translation_sampler": {
                    "input": "TIME",
                    "interpolation": "LINEAR",
                    "output": "translation"
                }
            }
        },
```

1.1ではこうなります。

```
    "animations": {
        "animation_0": {
            "channels": [
                {
                    "sampler": "animation_0_translation_sampler",
                    "target": {
                        "id": "_pPyramid1",
                        "path": "translation"
                    }
                }
            ],
            "samplers": {
                "animation_0_translation_sampler": {
                    "input": "animAccessor_0",
                    "interpolation": "LINEAR",
                    "output": "animAccessor_1"
                }
            }
        },
```

1.0では`sampler.input`や`sampler.output`での指定が、一旦`parameters`を挟んで間接的に`accessor`を指定していたのですが、1.1ではこの`parameters`が無くなって、直接`accessor`を指定するようになりました。

### material.parameter.value、 technique.parameter.valueがの値がスカラー値だった時も必ず配列に包むようになった

例えば、1.0では以下のように、スカラー値は配列の中に入っておらず、剥き出しでした。

```
    "materials": {
        "m0smiling_face_DF-fx": {
            "name": "m0smiling_face_DF",
            "technique": "technique0",
            "values": {
                "ambient": [    ←ベクトル値は配列の中に入っている
                    0,
                    0,
                    0,
                    1
                ],
                "diffuse": "texture_m0smiling_face_DF-diffuse-image",
                "emission": [    ←ベクトル値は配列の中に入っている
                    0,
                    0,
                    0,
                    1
                ],
                "shininess": 20,    ←スカラー値は、配列の中に入っておらず、剥き出しのNumberである
                "specular": "texture_m0smiling_face_DF-specular-image",
                "transparency": 0   ←スカラー値は、配列の中に入っておらず、剥き出しのNumberである
            }
        }
    },
```

1.1では、スカラー値も必ず配列に包まれています。

```
    "materials": {
        "m0smiling_face_DF-fx": {
            "name": "m0smiling_face_DF",
            "technique": "technique0",
            "values": {
                "ambient": [
                    0,
                    0,
                    0,
                    1
                ],
                "diffuse": "texture_m0smiling_face_DF-diffuse-image",
                "emission": [
                    0,
                    0,
                    0,
                    1
                ],
                "shininess": [20],    ←スカラー値も配列に包む
                "specular": "texture_m0smiling_face_DF-specular-image",
                "transparency": [0]   ←スカラー値も配列に包む
            }
        }
    },
```

余談ですが、`states.functions`（OpenGLのステート状態を指定する項目）の値の場合は、1.0でも1.1でも、スカラー値は配列に包むのが仕様のようです。
が、COLLADA2GLTFコンバーターで作成したglTFファイル（1.0準拠）の中には、スカラー値が剥き出しのものがありました。要注意です。（これ、コンバーターのバグではないだろうか。後で確認取れたらIssue報告しよう……）。

## 必須の項目が増えた

### `asset`項目の必須化

こんな感じの項目です。

```
    "asset": {
        "generator": "collada2gltf@bbe133925a39976f0172929f3838e16326b737c1",
        "premultipliedAlpha": true,
        "profile": {
            "api": "WebGL",
            "version": "1.0.2"
        },
        "version": "1.0"
    },
```

一番重要なのは、`asset.version`の数字です。これが、glTFのどのバージョンに準拠しているかを示しています。ここが、glTF1.1だと"1.1"になるわけですね。なので、ローダーとしては、まず最初にこのバージョン文字列を確認して、1.0/1.1どちらの読み込みモードで処理するかを判断すると良いでしょう。

気をつけなければならないのは、glTF1.0だとこのasset項目がオプショナルだということです。そのため、もし`asset`項目がない場合は、バージョン1.0準拠であると仮定して処理することになりますね。
（実は、1.0の前に0.8とかの古いglTF規格もあったのですが、さすがに正式版前のバージョンですし、無視して良いと思います）

## 追加または削除された項目がある

### 追加された項目

* Added accessor.normalized, #691, #706
* Added "STEP" as valid animation.sampler.interpolation value, #712
* Added glExtensionsUsed property and 5125 (UNSIGNED_INT) accessor.componentType value, #619
* Added extensionsRequired property, #720, #721

### 削除された項目

* Removed revision number from profile.version, #709
* Removed buffer.type, #786, #629
* Removed technique.functions.scissor and removed 3089 (SCISSOR_TEST) as a valid value for technique.states.enable, #681

### その他

　他にも、必須化された項目や、逆にオプショナルになった項目があります（個人的には、スキニングアニメーションで利用される[`skin.inverseBindMatrices`がオプショナルになった](https://github.com/KhronosGroup/glTF/issues/461)のは大きな変化だと思っています）。

　これ以上の細かい変更点については、glTF公式の[こちらのIssue](https://github.com/KhronosGroup/glTF/issues/605)に記載されていますので、ご覧ください。
　
# glTF1.1準拠のサンプルデータ

　さて、glTF1.1に自前のローダーを対応させるぞ、と思っても。実際に試験台となるデータが欲しいですよね。あります。

　glTF公式に深くコントリビュートされていらっしゃる、[javaglさん提供のサンプルデータ](https://github.com/javagl/gltfTutorialModels/blob/master/README.md)です。

![スクリーンショット 2016-12-30 19.14.04.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/50a06648-27b7-a6e3-ab15-cf843a2ff237.jpeg "スクリーンショット 2016-12-30 19.14.04.jpg")


　これらのサンプルデータ、1.1のテストとしてももちろんですが、それ以上に、自作したローダーがどこまでglTF仕様内の様々なケースにロバストに対応できているか、という点を試す上で非常に有用です。例えば...

* binファイルが２個以上の複数個に分かれている（一般的には.binファイルは１つのケースが多いので、それを前提にしたローダー実装だとロードに失敗する）
* 本当に必須の項目しか定義されていないサンプルがある（COLLADA2GLTFコンバータなどが常に入れているオプショナル要素が、当然あるものと思って固定的にそれらにアクセスしているローダーだとロードに失敗する）
* `states`系の項目をきちんと描画に反映しないと、正解のスクリーンショットとレンダリング結果が一致しない。そうした細かい部分を試すサンプルが用意されている（ちなみに私のローダーはそれまで面倒がって`states`を完全に無視していたので、当然壁にぶつかりました（笑））
* `material`と`technique`の両方で同名のパラメータを定義しているサンプルがあり、「どちらを優先すべきか」を間違えているローダーだと、色が正常に出ないようになっている（`material`の方を優先するのが正解）
* その他もろもろ

　私のローダーも、すでに十分ロバストだと思っていたんですが、これらのサンプルデータに１つ１つ対応するたびに、毎回こうした点につまづいて直したりしました。振り返ると、これらは教育効果を考えて注意深く作られた本当に良くできたサンプルデータだと思います（README.mdによると、コンバーターで作ったデータではなく、手作業で作りこんだサンプルデータなのだそうです）。

　また、余談ですがjavaglさんは[glTF Tutorial](https://github.com/javagl/glTF-Tutorials/tree/master/gltfTutorial#gltf-tutorial)というglTFの詳細なチュートリアル資料も作成されています。まだglTF公式として正式に取り込まれているわけではないようですが、内容を見てみると、図をふんだんに使って、かなり丁寧に説明がされていることがわかります。
　
![スクリーンショット 2016-12-30 19.15.50.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/9885f63e-a8ea-615d-1b9b-799cdace0f21.jpeg "スクリーンショット 2016-12-30 19.15.50.jpg")
　
　特に、スキニングアニメーションのチュートリアルなどは、これ以上ないくらいの分かりやすさです。glTFの理解を進めたい方は、必読のチュートリアルだと思います。

![スクリーンショット 2016-12-30 19.16.59.jpg](https://qiita-image-store.s3.amazonaws.com/0/12753/b108c703-1770-e030-4a64-b7d02a68c31b.jpeg "スクリーンショット 2016-12-30 19.16.59.jpg")

# 最後に

　glTFローダーを自作する際のコツ、そして来るglTFバージョン1.1について見てきました。
　glTF1.1は2016/12/30現在、ツール等の周辺環境の整備がまだ追いついていません。それどころか、再生を担う各種レンダリングライブラリですら、対応ができているプロダクトはごく限られています。

　glTF自体が正式版になってまだ間もないですし、ようやく1.0への対応が進んで、普及の兆しが見え始めたと思ったら<del>すぐにバージョン1.1が出る。
　このことが、glTFフォーマットの普及にどのような影響を与えるかは、正直まだわかりません。
　1.1に速やかに移行が進むのか、</del>はたまた1.0が広く使われ続けるのか……。何れにしても、バージョンの分断によって無用な混乱が起きないように、各種ツール・ライブラリ開発者は頑張らないといけないですね。

　でもですね。私個人の経験ですが、自作ローダーの1.1対応作業は、たった１日で済んだんですよ。それくらい、対応自体は簡単です。
　（むしろ、glTF規格内で可能なあらゆるデータ内容のバリエーションに、いかに各ツール・ライブラリがロバストに対応できるか、が今後のglTF普及の鍵ではないか、と感じています）

　<del>これからglTF対応を考えていらっしゃる方は、せっかくですから気合いで1.1にも頑張って対応しちゃえばいいんじゃないでしょうか。
　方針としては、まず最初に1.0への対応実装を行ってから、次に1.1向けにも対応できるようその実装を修正する、という方向でいけばいいと思います。</del>

　glTFは、今後も意欲的に新機能の導入が議論されており、それだけでなく、相互運用性やポータビリティにも同じくらい労力を割いて検討が行われている、素晴らしい３Dファイルフォーマットです。
　（今後は、GLSLやOpenGL系といった特定のシェーダー言語・3DAPIへの依存を減らしていく方向で議論が続けられています）

　個人的なひいき目を抜きにしても、glTFは今後、世の中の3Dデータがより広く流通するための、起爆剤・推進剤としての役割を担っていくようになるのではないでしょうか。[^1]
　皆さんも、今からglTFに慣れてしまいましょう！　
　
[^1]: プロダクションワークの方面では、最近PixarのUSDフォーマットが大変注目されています。今後、制作方面ではUSD、ランタイム再生方面ではglTFが、3Dデータフォーマットにおける期待のニューフェースとして、世に受け入れられていくのかもしれません。