今話題の3Dファイル形式「glTF」でWebGLの表現力を高めよう!

  • 66
    Like
  • 0
    Comment

 この記事は WebGL Advent Calendar 2016 の6日目の記事です。

 この記事は年を越しても(2017年)以降も、どんどん内容を充実させていきますので、よろしくお願いします。

はじめに 〜 glTFとは 〜

 glTF (The GL Transmission Format)とは、OpenGL系のAPI (WebGL, OpenGL ES, OpenGL) と親和性の高い、ランタイム用途向けのアセットフォーマットです。
 特にWebGLにとっては、特定ライブラリに依存しない、本格的な汎用3Dフォーマットの決定版とも言えるものです。

 最初の頃は COLLADA2jSON という名称で開発が行われていました。ここで名前が出てきましたが、もう10年ほど前、つまりPlaystation 3時代に、CG技術の業界標準化団体Khronosが普及に努めていたCOLLADAフォーマットというものがあります。
 このCOLLADA、DCC(MayaやBlenderなどの制作ツール)間のデータ交換フォーマットとして設計されていたため、ランタイム時の動的読み込みにはあまり向かないものでした(パーシングのスピードや、ローダーを作る際にそもそもの仕様の複雑さが障壁になったりなど)。1
 
 glTFは、ランタイム時の読み込み性能や、(COLLADAでの反省か?)フォーマット自体の簡潔さなどを重視して開発が進められています。現在、すでにバージョン1.0がリリースされ、すでに実用段階に達しています。来るバージョン1.1やさらに次のバージョンでは、PBR(物理ベース)マテリアル対応が予定されているなど、新技術への対応にも意欲的です。
 
 また、冒頭でOpenGL系と親和性が高い、と書きましたが、別にDirect3Dなどでも全く問題なく使うことができます。

 KhronosはこのglTFフォーマットを、「オーディオといえばMP3、ビデオといえばH.264、画像といえばJpeg、3Dといえば……glTF!」と言える状況を目指すとし、その気合の入れようは並々ならぬものがあります。
 
 最近では、インターネット等でファイルに含まれる情報種別の特定に使われる「MIMEタイプ」にもglTFが登録され、さらにエクスポーターやコンバーター、対応する3Dライブラリなどの周辺環境も加速度的に充実してきています。
 
 今や、最も将来が期待されているフォーマットの1つと言ってもいいかもしれません。

glTFでサポートされている要素

スタティックなモデルはもちろんのこと、スキニングアニメーションもサポートされています。また、シェーダーコードも吐き出してくれるため、レンダリング・スキニングについても具体的なシェーダー実装はglTFに任せることができます(もちろん、自分で変更しても良いわけですが)。

ただし、バージョン1.0ではまだブレンドシェイプ(頂点のモーフィング)はサポートされていません。

座標系と単位

  • glTFは右手座標系を使用します。つまり、xとyの外積はzを生成します。
  • glTFはy軸を上方向と定義します。
  • 全ての角度はラジアンで表現されます。
  • 反時計回りを正の回転とします。

 まぁ、ここら辺はまさにOpenGL系ですね。
 
 また、

  • すべての直線距離の単位はメートルです。

 なので、glTFに出力することを見越して、MayaなどのDCCツールでモデルを作成するときには、必ず長さの単位系を「メートル」に設定する必要があります。
 そうしないと、Mayaだとデフォルトでは「センチメートル」になっているため、そのままではモデル製作者の想定の100分の1で表示されることになってしまいます。

 最後に、

  • ノードのトランスフォーム値として設定される平行移動、回転、スケール(いわゆるTRS)の値ですが、そこから変換行列を作成する場合、T * R * Sの並びとして考え、後ろから掛け算します。つまり、頂点にはまずスケール行列をかけ、次に回転行列をかけ、最後に平行移動行列をかけます。

glTFの構成

 glTFの具体的な記述形式は、概要としてのメタ情報が記述されたJSONファイルと、頂点データやアニメーションデータなどが格納されたバイナリファイル、そしてGLSLシェーダーコードが書かれた.glslファイル、(そしてテクスチャを使う場合は通常の画像ファイル)です。つまり、これら複数のファイルから構成されています。
 と、いうのは実は基本形で、シェーダーコードや頂点データ&アニメーションデータ、画像データなどをData URLとしてJSONに埋め込んだバージョンや、本来のJSON部分も含めて全てが1つのバイナリファイルになっている.glb形式など、glTFフォーマットには(ファイルへの具体的なデータ格納の仕方という意味での)幾つかの派生系があります。

 とはいえ、基本はJSON+バイナリファイル+.glslファイル(+画像ファイル)だと思ってください。自分でローダーを書く場合も、最初はこの基本形に対応させると良いでしょう。
 

JSON部分の記述形式

 まずは、説明よりも先にファイルの中身を実際に見てみましょう。
 
 これは単純なキューブの一番簡単なモデルです。
 
 中身を抜粋すると...(わかりやすさのために、データの表記の順番を一部入れ替えています)

    "scene": "defaultScene",
    "scenes": {
        "defaultScene": {
            "nodes": [
                "node_1"
            ]
        }
    },
    "nodes": {
            "node_1": {
            "children": [
                "Geometry-mesh002Node"
            ],
            "matrix": [
                1,
                0,
                0,
                0,
                0,
                0,
                -1,
                0,
                0,
                1,
                0,
                0,
                0,
                0,
                0,
                1
            ],
            "name": "Y_UP_Transform"
        },
        "Geometry-mesh002Node": {
            "children": [],
            "matrix": [
                1,
                0,
                0,
                0,
                0,
                1,
                0,
                0,
                0,
                0,
                1,
                0,
                0,
                0,
                0,
                1
            ],
            "meshes": [
                "Geometry-mesh002"
            ],
            "name": "Mesh"
        }
    },
    "meshes": {
        "Geometry-mesh002": {
            "name": "Mesh",
            "primitives": [
                {
                    "attributes": {
                        "NORMAL": "accessor_25",
                        "POSITION": "accessor_23"
                    },
                    "indices": "accessor_21",
                    "material": "Effect-Red",
                    "mode": 4
                }
            ]
        }
    },

 総じて言うと、

  • まず各データ種類(nodeやmesh)ごとに、それらを連想配列でまとめたnodesmeshesといったものが、JSONの第一階層に存在します。例えばnodesの中には、連想配列で複数のnodeの具体的なメタ情報が定義されています。
  • 各データ項目(nodeなど)は、自身が保持する情報として、具体的な即値(行列値など)を保持している場合もあれば、他のデータ項目のキー文字列を指し示している場合もあります。
  • あるデータ項目の定義(例えば、あるnode)を見ている時に、それが持つあるデータが他のデータ項目のキー文字列を指し示している(例えば"meshes": [ "Geometry-mesh002"],)場合は、JSONの第一階層のその対応するデータ種類の連想配列を、先ほどのキー文字列("Geometry-mesh002")でアクセスすることで、その具体的な情報(そのnodeが持つmeshの具体的な情報)を得ることができます。
  • 基本的に、glTFのJSONファイルの読み込み手続きは、その参照の繰り返しです。

 また、glTFの公式Githubページでは、非常にわかりやすいチートシートが用意されています。

 これをベースに私の方でも即興で図を作ってみました。

gltf.png

glTFファイルの作成方法

 仕様はだいたいわかった。でも実際にglTFファイルを作れないと話になりませんね。
 方法は幾つかあります。今の所、もっとも確実な方法は、一度DCCツールから3DシーンをCOLLADA形式として出力し、それをCOLLADA2GLTFコンバーターでglTFファイルに変換する方法です。

 「え、なんでここでCOLLADAが入ってくんのよ!」そう思いますよね? 私も最初それ脳内でツッコみましたw

 でも、もともとCOLLADA2jSONとして開発されていた経緯や、できたてほやほやのglTFと違い、すでに各種DCCツールで出力環境が整っているのがCOLLADAであることを考えてみてください。
 各種DCCにglTF用のエクスポーターが実装されるのを首を長くして待つより、すでにある各種DCCのCOLLADAエクスポータでCOLLADA出力したものをglTFに変換した方が「現時点では」確実、という判断だったのかな? と私は想像しています。(実際のところの経緯は分かりません^^;)

 ここでは、このコンバーターの導入と使用方法について説明します。

glTFリポジトリのクローン

 COLLADA2GLTFのページに行きます。
 
 このリポジトリをクローンして、このWikiの手順に従って、COLLADA2GLTFコンバーターをビルドしてください。
 
(ビルド、と聞いて「面倒だなぁ」と感じた方は、オンラインで変換できるサービスがありますので、そちらの利用を検討してください。)
 
 少なくとも、私はMac環境でこの手順で問題なくコンバーターがビルドでき、利用できていることを確認しています。
 (万一、リビジョンによってはビルドやcmakeに失敗する場合があるかもしれません。少なくとも、私のMacOS El Capitan環境では、6779c09 で導入に成功したことを確認しています。Mac環境でハマった人はこのリビジョンを試してみましょう。)

コンバーターcollada2gltfがビルドできたら、早速使ってみましょう。COLLADA(.dae)ファイルを用意して、下記のようにしてコンバーターで変換します。

$ collada2gltf foo.dae

すると、下記4つのファイルができるはずです。

foo.gltf
foo.bin
foo0FS.glsl
foo0VS.glsl

ちなみに、collada2gltfには様々なオプションがあります。
(オプションによっては、出力の形式が変わり、出力されるファイル群が異なる場合があります)
いろいろ調べて試してみてください。

 なお、このアプローチではCOLLADAファイルをまず出力しなければなりませんが、Blenderの場合は標準でCOLLADAエクスポーターが搭載されています。
 Maya/3dsMaxの場合は、ここからCOLLADAエクスポーターをダウンロードし、導入してください。

 ちなみに私はメインツールがMayaなのですが、このCOLLADAエクスポーターとCOLLADA2GLTFコンバーターを使って生成したスキニングアニメーションglTFファイルが、自作のWebGLライブラリで問題なく再生できるところまでは確認しています。

バイナリデータへのアクセス

 さて、大体のデータ構造はわかりましたが、実際にglTFのモデルをWebGLなどの3D APIで画面に表示するためには、JSON部分のメタデータだけでは情報が足りません。
 実際のモデルの頂点データなどは、.binファイルにバイナリデータとして収められています。つまり、バイナリデータへのアクセスが必要なわけですね。

 じゃあ、具体的にどうやってアクセスすんの、という部分については、.gltfファイルのbuffersbufferViewsaccessorsの項目が教えてくれます。
 (先ほどの私のチートシートにも書いてありますので、ご覧ください!)

buffers

 まずは、buffersについて見てみましょう。以下にサンプル例を示します。

    "buffers": {
        "Box": {
            "byteLength": 648,
            "type": "arraybuffer",
            "uri": "Box.bin"
        }
    },

 Box.bin という実際の.binバイナリファイルが指定されており、またそのデータ長が648バイトであると書かれていますね。まぁ、ここは難しくないですね。

bufferViews

 次に、bufferViewsを見てみます。サンプルを示します。

    "bufferViews": {
        "bufferView_29": {
            "buffer": "Box",
            "byteLength": 72,
            "byteOffset": 0,
            "target": 34963
        },
        "bufferView_30": {
            "buffer": "Box",
            "byteLength": 576,
            "byteOffset": 72,
            "target": 34962
        }
    },

 このbufferViewは何かというと、前述のbufferが示す大元のバイナリデータの、サブセット領域を定義するものです。
"buffer"で大元のバイナリデータへの参照を、
"byteLength"でこのサブセット領域のバイト長を、
"byteOffset"でこのサブセット領域が大元のバイナリデータの先頭から何バイト目から始まるのか、
をそれぞれ示しています。
また、"target": 3496334963 は OpenGLマクロ定数でいうところの ELEMENT_ARRAY_BUFFERを指しており、つまりインデックスバッファとして使われることを意味しています。
"target: ": 3496234962ARRAY_BUFFERつまり、頂点バッファとして使われることを意味しています。

(コラム):glTFの値で時々出てくる謎の整数値は何?

例えば、先ほどの

    "bufferViews": {
        "bufferView_29": {
            "buffer": "Box",
            "byteLength": 72,
            "byteOffset": 0,
            "target": 34963
        },

 でいうなら、 34963 ですよね。glTFでは、こういう謎の整数値がバンバン出てきます。こういうのを見つけたら、ここを参照してください。そうです。つまりこれらはWebGL(OpenGL)のマクロ定数です。

 例えば 34963 を調べてみると、 ELEMENT_ARRAY_BUFFER を意味していることがわかります。ぱっと見の視認性は悪いですが、プログラムで実際にパースするときは、文字列ではなく整数値なので、直接の判別処理ができるため、ヘタに文字列であるよりこちらの方がいいですね。
 ただ、GL系以外の3D API処理系でglTFを扱う際は、同様の対応表をプログラムに用意させるか、gl.hなどのOpenGLのヘッダファイルをインクルードする必要があるため、そこはちょっと不便かもしれませんね。

 閑話休題。

accessors

 さて、次はaccessorsを見てみます。サンプルを示します。

    "accessors": {
        "accessor_21": {
            "bufferView": "bufferView_29",
            "byteOffset": 0,
            "byteStride": 0,
            "componentType": 5123,
            "count": 36,
            "type": "SCALAR"
        },
        "accessor_25": {
            "bufferView": "bufferView_30",
            "byteOffset": 288,
            "byteStride": 12,
            "componentType": 5126,
            "count": 24,
            "max": [
                1,
                1,
                1
            ],
            "min": [
                -1,
                -1,
                -1
            ],
            "type": "VEC3"
        }

 accessorは、前述の“bufferViews”で定義されているバッファのサブ領域内から、具体的にどのような形で、1つ1つのデータ(例えば頂点座標など)を読んでいけばいいかを定義しています。

 "accessor_25"を見てみると、
"bufferView": で参照するバイナリデータのサブセット領域を指定しています。
そして、
"byteOffset"で、そのサブセット領域の先頭から何バイト目から読み込みを開始すべきかを指定しています。
"byteStride"では「1つ分」のデータと、次の「1つ分」のデータとの間の、読み取り場所の移動バイト長を示しています。
どういうことかというと、この"byteOffset"と"byteStride"、それぞれ、OpenGL/WebGLのgl.vertexAttribPointer関数で指定する、オフセットとストライドに対応すると思ってください。
 (なので、今の所ほとんどのデータはそうなっていないですが、もしかしたらそのうちインターリーブなデータレイアウトのファイルも出てくるかもしれませんね。)
"componentType": 5126はOpenGL/WebGLマクロ定数でいうところのFLOAT型を意味しています。
"count"は、何個分データが入っているか、ですね。
"type": は、データがスカラー型なのか、ベクトル型(VEC2/VEC3/VEC4)なのかを示しています。
"max"と"min"は、このaccessor内の全データでの最大値と最小値ですね。
 
"accessor_21"の方も見てみましょう。
 "byteStride"が0となっていますが、0は特別扱いで、1つ1つのデータはぴったり隣り合っていることを意味しています。
 "componentType": 5123UNSIGNED_SHORTを意味しており、"type": "SCALAR"なので、これは頂点インデックスの情報なのでしょうね。
 
 
 と、まぁこんなところです。ここまで情報が提示されていれば、ちゃんとバイナリデータからデータを取ってくることができます。そして、今まで見て分かる通り、かなりOpenGL系を意識した構造になっていることがわかりますね。
 (まぁ、Direct3Dで扱う場合も、概念は似たようなものなのでなんとかなると思いますが。)

マテリアルの扱い

 さて、glTFではマテリアルの扱いはどんな感じなのでしょうか。拡張を使う場合と、標準の場合があり、標準だと以下のような感じです。

"materials": {
    "blinn-1": {
        "technique": "technique1",
        "values": {
            "ambient": [
                0,
                0,
                0,
                1
            ],
            "diffuse": "texture_file2",
            "emission": [
                0,
                0,
                0,
                1
            ],
            "shininess": 38.4,
            "specular": [
                0,
                0,
                0,
                1
            ]
        }
        "name": "blinn1"
    }
},

"techniques": {
    "technique1": {
         // parameter definitions
        "parameters": {
            "ambient": {
                "type": 35666
            },
            "diffuse": {
                "type": 35678
            },
            "light0Color": {
                "type": 35665,
                "value": [
                    1,
                    1,
                    1
                ]
            },
            "light0Transform": {
                "semantic": "MODELVIEW",
                "node": "directionalLight1",
                "type": 35676
            }
            // more parameters
        },
        // program, attributes and uniforms definitions
        "attributes": {
            "a_normal": "normal",
            "a_position": "position",
            "a_texcoord0": "texcoord0"
        },
        "program": "program_0",
        "uniforms": {
            "u_ambient": "ambient",
            "u_diffuse": "diffuse",
            "u_emission": "emission",
            "u_light0Color": "light0Color",
            "u_light0Transform": "light0Transform",
            "u_modelViewMatrix": "modelViewMatrix",
            "u_normalMatrix": "normalMatrix",
            "u_projectionMatrix": "projectionMatrix",
            "u_shininess": "shininess",
            "u_specular": "specular"
        },
        // render states
        "states": {
            "enable": [
                2884,
                2929
            ]
        }

上のglTFデータの場合は、materialsの下に、一つ blinn-1というマテリアルがありますね。それがさらに technique1 というtechniqueデータを持っています。このtechniqueは何かというと、マテリアルとシェーダーの橋渡し的な役割のデータになります。

 具体的には、
programで、他の場所で定義しているシェーダープログラムを指定し、
attributesで、入力とする頂点データの種類を定義し、
uniformsで必要となるuniform変数を定義し、
statesで、この3Dオブジェクトの描画の際に有効/無効にするWebGLのステート(例えばgl.enable(gl.BLEND)とかですね)を指定し、
parametersで、uniformsで指定している各種uniform変数のデータ型や値(オプショナル)などのパラメーターを定義しています。

そして、先ほどの blinn-1マテリアルで、valuesの下にambientdiffuseshininessといった具体的なマテリアルパラメータが定義されていますが、それらがtechniqueuniformsの下の項目群の中のいずれかと対応していることに注目してください。uniformsの項目群で、具体的な値が設定されていないもの(light0Color以外がそうですね)については、このblinn-1マテリアルのvaluesの項目の値が、実際のシェーディング時に利用されます。
もし、technique.uniforms以下とmaterial.values以下の両方で、同名の項目があった場合は、material側の項目のパラメーター値が優先されます。

 勘の良い方は、light0Colorつまりライトに関しては具体的な色がtechnique側で指定されていて、マテリアル側のvalues下には、ライトの具体的な情報が指定されていないことに気がつかれたと思います。マテリアルはその意味からして、ライト情報とは切り離されているべきです。その点でもこのようになっているのは自然ですね。

 また、glTFの仕様上、material項目のtechniqueプロパティは必須ではなくオプショナルになります。
 このtechnieqeプロパティが存在せず、さらに別にマテリアルを定義するglTF拡張(後述します)も存在しない場合、3Dオブジェクトは、50%灰色の放射色を持つデフォルトのマテリアルを使用してレンダリングされます。
 デフォルトのマテリアルとはどういうものかについては、glTF仕様書の付録Aを参照してください。まぁ、とどのつまり、ただの灰色(0.5, 0.5, 0.5, 1.0)での塗りつぶしです。
 
 実際には、ほとんどのglTFアセットでは、techniqueプロパティまたはマテリアル系のglTF拡張によってマテリアル情報を持っている場合がほとんどです。 デフォルトのマテリアル(灰色)は本来、拡張マテリアルが使用されているときにアセットが何らかの明示的なテクニックをいちいち定義しなくて良いようにと用意されているものです。
 

KHR_materials_commonマテリアル拡張について

 前述のマテリアルについての説明を読んで、薄々感じられた方もいらっしゃるかもしれませんが、glTFでは具体的なシェーディングモデル(Lambert、Blinn、Cook-Torranceなど)については特に規定していません。
 先ほどのglTFデータ例でblinn-1とありましたが、あれはあくまで「そういう名前」の項目名があっただけに過ぎず、実際のBlinnのシェーディングモデルは、blinn-1techniqueで指定しているtechnique-1programで指定しているprogram_0の(長ったらしいなおい)シェーダープログラムの中に、実際のGLSLのシェーダーコードとして実装が存在します。
 つまり、glTFそのものはシェーディングモデルについて特に関知せず、付属で指定しているシェーダーコードにそこらへんの詳細はぶん投げているわけです。

 しかし、多くの場合(最新の物理ベースのCGゲームとかの場合を除く)、使いたい古典的なシェーディングモデルというのは決まっていますね。いちいちシェーダーコードでそこらへん記述せんでもええやん、と。

 そこで便利なのが、KHR_materials_common マテリアル拡張です。

 このglTFの拡張記述形式では、以下のような記述の形で、Blinn, Phong, Lambert と Constant(一定色)といった、よく知られる古典的なマテリアルと

"materials": {
    "lambert1": {
        "extensions": {
            "KHR_materials_common" : {
                // one of CONSTANT,
                // BLINN,
                // PHONG,
                // LAMBERT
                "technique" : "LAMBERT",
                "values": {
                    "diffuse": [
                        0.5,
                        0.5,
                        0.5,
                        1
                    ]
                }
            }
        }
    }
}

 以下のような記述で、ambient, directional, point or spotといった種類のライトを指定することができます。

"extensions": {
    "KHR_materials_common" : {
        "lights": {
            "light1": {
                "directional": {
                    "color": [
                        1,
                        1,
                        1
                    ]
                },
                "type": "directional"
            }
        }
    }
}

 さて、前述のglTF標準のマテリアル・テクニックとの違いは何でしょうか?
 glTF標準のマテリアル・テクニックの場合は、具体的なライティング(シェーディング)処理の詳細がシェーダーコードに書かれています。「シェーダーに与えるパラメータについては、glTFのマテリアル・テクニックで指定するけど、具体的なライティング・シェーディング処理(アルゴリズム)についてはGLSLコード読め!」ってことですね。

 一方の KHR_materials_commonマテリアル拡張では、公式のGithubページのREADMEに書かれている通り、Blinnなどのシェーディングモデルのアルゴリズム(計算式)やスポットライトのパラメータの扱いなどについて、あらかじめ規定がされています(公式のREADMEに書かれていますので読んでください)。

 そのため、glTFを扱うDCCのエクスポーターやレンダリングエンジンが、そのKHR_materials_commonマテリアル拡張の規定(お約束)に従ってデータをやり取り・解釈することで、シェーダーコード不要でコンパクトにライティングのワークフローが組めるわけです。

 先進的かどうかはともかく、便利は便利ですね。

glTF Next以降のPBR(物理ベースマテリアル)について

 さて、glTFのPBRマテリアルについては、実はこの記事を書いている2016/11/15現在でも、まだ公式Github上で議論が続けられている状況です(glTF Nextと暫定的に呼ばれています)。
 そのため、まだ本記事で具体的に取り上げられる状態ではないのですが、面白いIssueのやり取りを紹介しましょう。
 
 こちらです。

最初の書き込み。

The goal of PBR in glTF is to transmit high-quality 3D models to foreign engines while maintaining separation of concerns with existing lights, cameras, objects, and environments in those engines.

「glTFにおけるPBRの目標は、既存のライト・カメラ・オブジェクトやそうした外部エンジンの環境との関心の分離を維持しながら、高品質の3Dモデルを外部のエンジンに持ち込むことです」

 とあります。引き続き読んでいくと、大体こんな趣旨っぽい(私 @emadurandalの解釈が多分に入っています)。

「glTFファイルのシェーダーコードだけど、というか現在のリアルタイムCG技術のシェーダーコードは、マテリアルだけでなく周囲の環境(ライトや環境マップ)などのことが書かれすぎていて、切り分けがちゃんとできていない。だから、そのモデルを他のエンジンの環境に放り込むと、当然馴染まない」

「通常のマテリアルモデルは、それが本来作られた環境でのライトなどの環境と密接に関わっているのが普通。しかしPBRならそうした事情がなく、物理特性という世界共通の基準で作られているので、他の環境のライトでも同じ見栄えになりやすい」

「つまり、PBRを採用することで、glTFモデルと周囲の環境(ライトなど)との依存性を切り離すことができる」

「それぞれのエンジンのライティング環境の違いなどをあまり気にせず、モデルデータをやりとりできるよね。それこそがglTFがPBRを採用するモチベーションだと思うんだけど。みんなどう思う?」
 
 というのが、このIssueのやり取りの議題のようです。

 特に面白いのが、次の一節。

The bar set by KHR_materials_common isn't just laying on the floor, it sank into a tarpit accompanied by some fixed-function dinosaur bones.

KHR_materials_commonマテリアル拡張がもたらす(技術)水準は、地べたを這いずり回っているどころじゃない。幾つかの固定機能の恐竜の骨とともにタール坑に沈んでいる」

(※恐竜には時代遅れという意味合いがあります)

 という、英語ならではのウィットに富んだジョークで、KHR_materials_commonマテリアル拡張ディスられまくってるじゃないすか!www

 まぁ、KHR_materials_commonマテリアル拡張はこれはこれで使い所によっては便利だと思うんですけどね(実際、このIssueでもそういう意見は出てる)。

 いずれにせよ、将来のglTFのPBRサポートによって、異なるDCCやレンダリングエンジン間のモデルデータの取り回しの状況が、大きく改善することを期待したいですね。仕様が固まり次第、別のQiita記事にまた纏めようと思います。
 
 2016/12/30追記:
 glTF公式としては、PBRはまだ議論中ですが、すでにglTFを独自拡張してPBRを先行実装してしまった事例があるようです。
 上記のサイトからglTFファイルを落として、中を開いてみると分かりますが、具体的には、FRAUNHOFER_materials_pbrという拡張が定義されており、この拡張部分とPBR関連テクスチャ(メタルネスやラフネスのテクスチャなど)を使ってPBRが実現されているようです。この拡張、すでにglTF公式にプルリクエストされていまして、もしかしたら採用・またはglTF公式としての仕様に大きな影響を与えることになるかもしれませんね。

付属のGLSLシェーダーコードもちゃんと読み込もう!

 これは、私がハマった体験談なんですが、glTFに付属している(あるいは、glTF内にData URIなどで埋め込まれている)GLSLシェーダーコードは無視せずに、ちゃんと読み込んだ方が無難です。

 私も当初は「どうせ、簡単なライティングとかの計算が入っているだけでしょ? 自前のエンジンがあるから、マテリアルパラメータだけ読み取って、あとは自前のライティング処理でイケるイケル」

とか余裕こいていたのですが、はまったデータがあります。これです。

 これ、よく見てみるとUV座標値が大きな整数値になっていて、しかも二次元テクスチャなのに3要素(Vec3)あるのです。これ、そのまま自前のローダーで読み込んだら案の定テクスチャが表示されませんでした。
 調べていくと、どうやら、z要素でxとy(uとv)を割ることを前提にしたデータのようです。このデータの提供元であるCesium.jsのフォーラムで聞いてみたところ、
 「float型(4バイト)のvec2でテクスチャ座標を表現するより、short型(2バイト)のvec3の方が、一つのUVごとに2バイト分節約できるから」
 ということのようです。なるほど…。

 「そんな暗黙の了解みたいなものを勝手にやられても、ファイル読む方は困るじゃん」と思っていたのですが、よく見たらこのglTFファイルにData URIで埋め込まれている頂点シェーダーに、そこらへんを良しなにやってくれるコードが入っていたというオチでした。

 というわけで、glTFのデータ部分はあくまでデータの塊なのであって、(KHR_materials_common拡張のような、あらかじめ決められたデータの解釈の取り決めに従っている場合を除き)、付属のGLSLシェーダーコードも含めてセットで読まないと、正しく扱えない場合がある、ということに注意してください。

 しかし、こういうのってあまり良くないと思うんですよね。GLSLコードですから。DirectXなどの、GL系以外の処理系だとそのまま扱えないわけでして、再生環境を選んでしまうではないですか。
 3D APIの処理系を超えたポータビリティを考えると、シェーダーコードに過度に依存しないglTFデータを作成するように心がけた方がいいですね。

 追記:この問題については、公式Githubでも議論されているようです。
 来るglTF Next以降では、GLSLというか特定の3D APIに依存しないフォーマットを志向しているようです。

各種ツールの紹介

 さて、ここで、glTFをサポートする各種ツールをご紹介しましょう。

COLLADA2GLTF以外のglTFコンバーター

obj2gltf

 Obj形式からglTFへのコンバーターです。COLLADAヘの出力が難しい場合に、こちらを利用すると良いでしょう。

COLLADA|OBJ to glTF

 前述の COLLADA2GLTFobj2gltf はコマンドラインツールでした。特に、COLLADA2GLTF は最初にコンバーターをビルドする必要があり、面倒に感じる方もおられるでしょう。
 この COLLADA|OBJ to glTF はコマンドラインツールではなくWebサービスで、このページ上にCOLLADAファイルまたはObjファイルをドラッグ&ドロップするだけで、glTFに変換したものが自動的にダウンロードされます。また、ページ上のビューアーにもドラッグ&ドロップしたファイルの内容が表示されるので、簡易ビューアーとしてもおすすめです。

JavaScript製のglTFローダー(WebGLライブラリ)

 まぁ、Three.jsとかBabylon.jsとかは定番ですね。
 ここでは4日目の @cx20 さんの記事「glTF 対応 WebGL ライブラリを比較してみる」の紹介をすることで、説明と代えさせていただきます。
 
 スキニングアニメーションも含めたglTFのサポートレベル、何気に私の自作WebGLライブラリGLBoost@kyasbal さんたちが開発する Grimoire.js といった、国産ライブラリ勢が結構頑張ってますよ!

JavaScript以外の代表的なglTFローダー

Java

JglTF

 今の所、JavaでglTFというとこれ一択のようです(2016/11/6現在)。
 私も触りましたが、ローダーとしての正確性はかなり高い印象です。
 特に、glTFのスキニングアニメーションは、きちんとロバストに対応しているライブラリは少ないのですが、このJglTFはバッチリです。
 ちなみに(個人的な話で恐縮ですが)私emadurandalのWebGLライブラリ「GLBoost」でのスキニングアニメーションのサポートにおいては、このJglTFの実装や動作をかなり参考にさせていただきました。
 他のライブラリの多くが、私がMayaで作ったglTFデータ、もしくはglTF公式のglTFサンプルデータ、どちらか一方しか正しく再生できない中で、当時唯一このJglTFだけが両方きちんとスキニングアニメーションを再生できていました。

C++

Tiny glTF loader

 レイトレ技術で名高い @syoyo さんが開発されたローダーです。失礼ながら、まだ私は実際に試したことがないのですが、syoyoさん製であればきっとその品質も期待できるものではないかと思います。
 ヘッダーオンリーなので、ビルドしなくてもインクルードするだけで使えるようです。
 TODOリストに「Parse skin」とあるので、まだスキニングアニメーションには対応していないの……かな?(2016/11/6現在)

その他、ここでは紹介しきれなかったサポート環境

 glTFのGithubページのREADME.mdには、他にも多くのglTFをサポートするライブラリ、コンバーター、エクスポーターなどが紹介されています。

最後に

 いかがでしょうか。glTFはサポートしている機能の多さ(スキニングや将来の物理ベースマテリアル対応、拡張仕様など)や、読み込みの容易さなどから、非常に有力な3Dファイルフォーマットの一つです。
 特に今のところ、WebGL用途においては、これだけの機能を有し、かつ汎用的に利用できる3DファイルフォーマットとしてはglTF一択となる状況が続きそうです。すでに豊富なエコシステム(再生環境、エクスポーター、コンバーターなど)を有していますし、すでにフェーズとしては実用段階に入っています。

 WebGL自体ようやく、バージョン1.0の実行環境の普及がほぼ完了してまだ日が浅いですが、これまでは複雑なモデルを用いたデモ(特に複雑なアニメーション・スキニングなどもついているもの)を見かけることは少なかったように思います。ないわけではなかったのでしょうが、その場合は独自フォーマットなどでやられていたのかもしれません(あるいは、Three.jsが専用のファイルフォーマットで結構なことができた気はしますが、もちろんそれはThree.jsでしか使えないわけで…)。

 ですが、これからはglTFがあります。
 glTFは特定のライブラリに依存せず、多くの機能をサポートしています。これから、多くのWebGL案件で利用され、Web3Dにおける表現の幅が広がることを期待しています。
 この記事をとっかかりに、どんどんglTFにチャレンジしてください!
 
 あ、最後に。実際の動作例として、私の拙作WebGLライブラリ「GLBoost」で、Khronos公式のサンプルglTFファイルを読んだ例を挙げてみますね。スキニングアニメーションを再生しているだけですが、まぁこれくらいのことは普通にできちゃいます。

 glTFでのスキニングアニメーションの再生については、また別の機会に別記事にて詳細を書いてみたいと思います。
 
 それでは皆様もよきWebGL & glTFライフを!
 


  1. COLLADAフォーマットは、数々の3Dファイル形式が乱立する混沌とした状況を打開すべく、「このフォーマットでデータ互換の問題を解決する!」という万能のフォーマットを目指して開発・普及活動が行われていました。数々のインポーター・エクスポーターでの互換性問題を解決するためのバリデーターや互換性認定制度など、当時としては様々よく考えられていたプロジェクトでしたが、結局のところ、当初の「CG業界における互換フォーマットの決定版となる」という目的は果たせませんでした。普及度・活用度ではDCCツール市場で圧倒的シェアを誇るAutodesk社のFBXフォーマットに遅れをとり、結局ただの1フォーマットに留まってしまっている現状です。そして、今や新たにAlembicフォーマットやPixarのUSDフォーマットなど、3Dシーンを表現するフォーマットはまだまだ登場しています。COLLADAが夢見た「CG業界における決定的な統一互換フォーマット」の夢は、果たしていつか実現することはあるのでしょうか?