OpenGL(webGL)向けの、ランタイム用のアセットのフォーマットを見てみたいと思います。
[glTF – Runtime asset format for WebGL, OpenGL ES, and OpenGL]
glTFとは
もともとは、COLLADA2jSONとして作られたもののようですが、現在はglTFという名称になっています。KhronosのCOLLADAワーキンググループで、仕様の策定が行われているようです。
COLLADAフォーマットは、DCCツール間のアセット交換や出力を主目的として作られているため、ランタイムでの読み込みという意味では、抽象的過ぎて扱いづらい側面がありましたが、glTFは、ランタイムでの読み込みと再生を主眼に置いたものとなっているようです。
前身となったCOLLADA2jSONの名が示すとおり、JSON形式が出力フォーマットですが、コンバーターのwriterクラスを書き換えれば、任意の形式で格納可能だと思われますが、当面はWebGLがメインのプラットフォームだとみなすことができると思います。ちなみに、TFはTransmissionFormatの略だそうです。
glTFの中身
アニメーションのデータなど入るようですが、とりあえずはジオメトリのデータの格納構造を見てみたいと思います。glTFのgitを取得すると、サンプルのモデルとして、duckが入っているので、これを見てみます。
https://github.com/KhronosGroup/glTF
頂点配列の格納形式
頂点配列に限らず、おそらく大きな配列は以下の構造になると思われます。”buffers”内に、配列の実体となるファイルや、内容そのものの、配列が定義されるようです。
“bufferViews”には、”buffers”内に定義された実体に対し、各要素に対応するビューを定義します。
下記の例では、ducks.binファイルに格納されたbufferに、頂点データ配列と、頂点インデックス配列のためのbufferViewを定義しています。
"bufferViews": { "bufferView_24": { "buffer": "duck.bin", "byteLength": 76768, "byteOffset": 0, "target": "ARRAY_BUFFER" }, "bufferView_25": { "buffer": "duck.bin", "byteLength": 25272, "byteOffset": 76768, "target": "ELEMENT_ARRAY_BUFFER" } }, "buffers": { "duck.bin": { "byteLength": 102040, "path": "duck.bin" } },
頂点アトリビュートの格納形式
頂点アトリビュートは、頂点バッファのbufferViewを参照し、offset,stride,countとtypeを定義します。
下記の例では、いずれも上記で定義した、”bufferView_24″のARRAY_BUFFERを参照し、
頂点位置、法線、UVと思われる3つのアトリビュートを定義しているようです。
"attributes": { "attribute_18": { "bufferView": "bufferView_24", "byteOffset": 0, "byteStride": 12, "count": 2399, "max": [ 96.1799, 163.97, 53.9252 ], "min": [ -69.2985, 9.92937, -61.3282 ], "type": "FLOAT_VEC3" }, "attribute_20": { "bufferView": "bufferView_24", "byteOffset": 28788, "byteStride": 12, "count": 2399, "max": [ 0.999599, 0.999581, 0.998436 ], "min": [ -0.999084, -1, -0.999832 ], "type": "FLOAT_VEC3" }, "attribute_22": { "bufferView": "bufferView_24", "byteOffset": 57576, "byteStride": 8, "count": 2399, "max": [ 0.983346, 0.980037 ], "min": [ 0.026409, 0.019963 ], "type": "FLOAT_VEC2" } },
プリミティブの格納形式
プリミティブでは、最初に定義した、頂点インデックスバッファのbufferViewを参照し、加えて、頂点アトリビュートと、セマンティクスの対応付けと、プリミティブ形状を定義します。
とりあえずここまでで、頂点バッファの作成から、DrawCallまでを行うことができそうな情報が格納されているのが分かります。
マテリアルも参照されていますが、これは後ほど見てみます。
"primitives": [ { "indices": { "bufferView": "bufferView_25", "byteOffset": 0, "count": 12636, "type": "UNSIGNED_SHORT" }, "material": "material.0", "primitive": "TRIANGLES", "semantics": { "NORMAL": "attribute_20", "POSITION": "attribute_18", "TEXCOORD_0": "attribute_22" } } ]
メッシュの格納形式
メッシュは、プリミティブの上位レイヤーに該当するもので、上記で説明した、頂点アトリビュートと、プリミティブの配列を内包します。
"meshes": { "LOD3spShape-lib": { "attributes": {.....}, "name": "LOD3spShape", "primitives": [.....] } },
ノードの格納形式
ノードは、メッシュや、カメラ、ライトなどの位置情報と親子関係を記述した、シーングラフに相当します。
ここまでの情報で、ModelViewマトリクスを設定して、DrawCallが行うことができそうな情報が格納されているのが分かります。
"nodes": { "node_0": { "children": [], "matrix": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], "meshes": [ "LOD3spShape-lib" ], "name": "LOD3sp" }, },
シェーダーの格納形式
まず、使用するシェーダーコードを、”shaders”に定義し、使用するシェーダーの組み合わを”programs”内に定義します。さらに、シェーダーが外部参照するパラメーター(UniformやVertexAttribute,Texture)と、使用すべきRenderStateを”techniques”に定義します。
"shaders": { "technique1Fs": { "path": "technique1Fs.glsl" }, "technique1Vs": { "path": "technique1Vs.glsl" } }, "programs": { "program_0": { "fragmentShader": "technique1Fs", "vertexShader": "technique1Vs" } }, "techniques": { "technique1": { "parameters": {}, "pass": "defaultPass", "passes": { "defaultPass": { "instanceProgram": { "attributes": [ { "semantic": "NORMAL", "symbol": "a_normal", "type": "FLOAT_VEC3" }, { "semantic": "POSITION", "symbol": "a_position", "type": "FLOAT_VEC3" }, { "semantic": "TEXCOORD_0", "symbol": "a_texcoord0", "type": "FLOAT_VEC2" } ], "program": "program_0", "uniforms": [ { "semantic": "WORLDVIEWINVERSETRANSPOSE", "symbol": "u_normalMatrix", "type": "FLOAT_MAT3" }, { "semantic": "WORLDVIEW", "symbol": "u_worldviewMatrix", "type": "FLOAT_MAT4" }, { "semantic": "PROJECTION", "symbol": "u_projectionMatrix", "type": "FLOAT_MAT4" }, { "parameter": "diffuse", "symbol": "u_diffuseTexture", "type": "SAMPLER_2D" } ] }, "states": { "blendEnable": false, "cullFaceEnable": true, "depthMask": true, "depthTestEnable": true } } } } },
マテリアルの格納形式
マテリアルは、自身が使用するtechniqueを参照し、シェーダーに対して設定すべき値が記述されています。
以下の例では、設定すべきテクスチャと、shininessの値があります。
shininessの値は、使用されているtechniqueに、Uniformの設定が無いので、おそらく無視されるのではないかと思われます。
"images": { "image_0": { "path": "duckCM.png" } }, "materials": { "material.0": { "name": "blinn3", "technique": "technique1", "techniques": { "technique1": { "parameters": { "diffuse": { "image": "image_0", "magFilter": "LINEAR", "minFilter": "LINEAR_MIPMAP_LINEAR", "type": "SAMPLER_2D", "wrapS": "REPEAT", "wrapT": "REPEAT" }, "shininess": { "type": "FLOAT", "value": 38.4 } } } } } },
とりあえず読み込んでみた
後学のため、自前でglTF-parser.jsを継承したオブジェクトを作って、サンプルにあるDuckのモデルを、読み込んでみました。(ちなみに、Duckのモデルは、model/duckと、loaders/threejs/duckに格納されているが、後者の方が新しいものと思われる。)
https://googledrive.com/host/0B79CBbJe5Ux1MF9yNFh5SlRZNlU/index.html
このプログラムは、汎用的には書いていないので、Duck以外のモデルは、おそらく正しくロードできないと思います。
ジオメトリやテクスチャ、シェーダーを読み込むあたりは比較的短時間で書けたのですが、マテリアル関連の部分が、少々面倒に感じました。
technique内のpassが配列である部分や、material内でtechniqueが配列である部分などは、少々冗長に感じます。その割には、samplerの設定がマテリアル内にあったりするので、このあたりは今後改善されていくのではないかと思います。