一、glTF数据格式
为了进行渲染,场景数据和3D几何数据需要转换为图形API可以接受的形式,从而可以被传输到显卡上进行渲染。通常,如下图所示,应用程序为此需要编写导入程序、加载程序。
glTF格式的目标是为3D内容的数据格式提供统一的标准,方便应用程序读取进行渲染。目前来说已经存在的3D数据格式不是没有包含场景数据,就是包含一些只能用于特定创作软件的数据,许多时候,需要对几何数据进行预处理才能直接用于渲染。
目前而言,现存的3D数据格式不能够方便地在互联网上进行传输,以及直接高效地进行渲染。glTF的目标是作为一个中转格式,而不是另一个新的3D数据格式:
- 使用JSON来描述场景结构,可以方便地被应用程序分析处理。
- 3D数据以一种可以被大多数图形API直接使用的方式进行存储,不需要应用程序进行解码或预处理操作。
官网详细图示:https://github.com/KhronosGroup/glTF#resources
二、glTF格式基本结构
glTF格式本质上是一个JSON文件。这一文件描述了整个3D场景的内容。它包含了对场景结构进行描述的场景图。场景中的3D对象通过场景结点引用网格进行定义。材质定义了3D对象的外观,动画定义了3D对象的变换操作(比如选择、平移操作)。蒙皮定义了3D对象如何进行骨骼变换,相机定义了渲染程序的视锥体设置。
json结构
场景对象以数组的形式存储在JSON文件的meshes中。可以通过对应的数组来索引访问:
"meshes" :
[
{ ... }
{ ... }
...
],
数组索引也被用来定义对象之间的关系。上面的代码定义了多个网格对象,场景中的一个结点node可以通过网格索引引用上面定义的其中一个网格对象:
"nodes":
[
{ "mesh": 0, ... },
{ "mesh": 5, ... },
...
}
下图给出了glTF格式的JSON部分的顶级元素概览:
元素说明:
- scene: glTF格式的场景结构描述条目。它通过引用node来定义场景图。
- node: 场景图中的一个结点。它可以包含一个变换(比如旋转或平移),引用更多的子结点。它可以引用网格和相机,以及描述网格变换的蒙皮。
- camera: 定义了用于渲染场景的视锥体配置。
- mesh: 描述了场景中出现的3D对象的网格数据。它引用的accessor对象可以用来访问真实的几何数据。它引用的material对象定义了3D对象的外观。
- skin: 定义了用于蒙皮的参数,参数的值通过一个accessor对象获得。
- animation: 描述了一些结点如何随时间进行变换(比如旋转或平移)。
- accessor:一个访问任意数据的抽象数据源。被mesh、skin和animation元素使用来提供几何数据,蒙皮参数和基于时间的动画值。它通过引用一个bufferView对象,来引用实际的二进制数据。
- material:包含了定义3D对象外观的参数。它通常引用了用于3D对象渲染的texture对象。
- texture:定义了一个sampler对象和一个image对象。sampler对象定义了image对象在3D对象上的张贴方式。
引用外部数据
二进制数据,比如3D对象的几何数据和纹理数据通常不被包含在JSON文件中,它们被存储在外部的文件中。JSON文件中只包含了到这些外部文件的链接。这使得二进制数据可以以非常紧凑的形式进行存储方便互联网传输,并且可以直接被渲染程序使用,无需额外的解码、预处理。
如上图所示,有两种类型的对象buffers和images可以包含外部文件链接。
读取和管理外部数据
读取和处理glTF格式文件从分析JSON结构开始,JSON结构被分析完后,就可以使用buffers和images数组来索引访问buffer和image对象。每个buffer和image对象引用了一块二进制数据。通常会将二进制数据读取到内存中,以它们在原来buffers和images数组中的索引顺序进行存储,以便使用相同的索引来访问对象对应的二进制数据。
buffers中的二进制数据
一个buffer包含了一个指向二进制数据的URI:
"buffer01": {
"byteLength": 12352,
"type": "arraybuffer",
"uri": "buffer01.bin"
}
二进制数据本质上是一个从buffer对象的URI处读取得到的内存块,没有任何层次和结构意义。二进制数据使用起来比JSON格式更加高效,便于在互联网传输使用,渲染程序无需解码、预处理就可以直接使用。
images中的图像数据
一个image对象可以引用一个外部图像文件来作为要渲染的3D对象的纹理:
"image01": {
"uri": "image01.png"
}
uri 通常指向一个PNG或JPG文件。这些格式的图像文件可以显著减小图像数据的体积,便于互联网传播。image对象也可以引用存储在buffer中的图像数据。
数据URI中的二进制数据
通常,buffer和image对象的uri指向了一个包含实际数据的文件,但也可以直接在uri中通过数据URI直接包含数据。
三、示例:一个最小巧的glTF文件
下面是一个最小巧的glTF格式文件的内容,它描述了一个简单的三角形。可以将下面的内容保存到一个gltf文件中,使用任意支持glTF格式文件的渲染程序来渲染它。
{
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0
}
],
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
"buffers" : [
{
"uri" : "data:application/octet-stream;base64,AAAA为了排版而省略,可以使用英文原文中的代码",
"byteLength" : 44
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : 6,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : 8,
"byteLength" : 36,
"target" : 34962
}
],
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5123,
"count" : 3,
"type" : "SCALAR",
"max" : [ 2 ],
"min" : [ 0 ]
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 3,
"type" : "VEC3",
"max" : [ 1.0, 1.0, 0.0 ],
"min" : [ 0.0, 0.0, 0.0 ]
}
],
"asset" : {
"version" : "2.0"
}
}

scene和nodes结构
glTF格式使用scene对象来描述场景。对glTF数据的JSON文件进行解析时,对场景结构的遍历也是从scene对象开始。每个scene对象引用了一个nodes数组,nodes数组通过索引引用了场景的根结点。
示例中的代码只包含了一个scene对象,这一scene对象引用了一个索引为0的node对象,这个node对象引用了索引为0的mesh对象:
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0
}
],
meshes
mesh对象用于表示场景中出现的3D对象实际的几何数据。mesh对象本身不包含任何属性,只包含了一个primitives对象数组,用于描述整个网格所使用的几何数据。每个primitive对象描述了一部分mesh对象的几何数据。
示例只包含了一个mesh对象,这一mesh对象只包含了一个primitive对象。primitive对象包含了一个attribute对象数组,存储了mesh对象的几何数据信息。对于这个示例来说,只包含了一个POSITION属性,用于描述mesh对象的顶点位置信息。primitive对象的indices属性描述了用于渲染的顶点索引数据,默认情况下,连续3个顶点索引构成一个三角形。
mesh对象实际的几何数据由primitive对象的attributes对象数组和indices对象数组通过引用accessor对象给出:
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
buffer,bufferView和accessor
buffer,bufferView和accessor对象提供了对mesh对象实际的几何数据的描述。
Buffers
buffer对象描述了一个没有任何结构和层次意义的数据块。它包含了一个uri属性,用于引用外部文件作为数据,或是直接使用数据URI作为数据内容。
对于示例,我们使用数据URI直接在JSON文件中编码长度为44字节的buffer数据:
"buffers" : [
{
"uri" : "data:application/octet-stream;base64,AAAAAAAAA为了排版而省略,可以使用英文原文中代码",
"byteLength" : 44
}
],
上面代码中uri属性指定的数据包含了一个三角形3个顶点的索引和顶点位置信息。本身buffer对象所指定的数据是没有任何结构和层次意义的,所以需要使用bufferView和accessor对象来描述数据的结构和层次意义。
Buffer views
一个bufferView对象引用了一个buffer对象的一部分数据。对于这个示例来说,包含了2个bufferView对象,它们引用了同一个buffer对象的不同部分数据。第一个bufferView对象引用了三角形的索引数据,数据位置从偏移值byteOffset(0)开始,长度为6字节。第二个bufferView对象引用了三角形的顶点位置数据,数据位置从偏移值byteOffset(8)开始,长度为36字节:
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : 6,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : 8,
"byteLength" : 36,
"target" : 34962
}
],
Accessors
accessor对象用于描述数据的结构和层次意义。它描述了bufferView所引用数据的解释方式。
对于本例,包含了两个accessor对象。
第一个accessor对象描述了顶点的索引数据,它引用了索引为0的bufferView对象,这一bufferView对象引用了顶点索引数据。accessor对象还包含了count,type和componentType3个属性,用来对数据进行描述。对于示例来说,这3个属性描述的的数据元素包含了3个类型为unsigned short的标量。
第二个accessor对象描述了顶点位置数据。它引用了索引为1的bufferView对象,这一bufferView对象引用了顶点位置数据。对于示例来说,这一accessor对象的count,type和componentType属性描述的数据元素的个数为3,每个数据元素是一个包含3个分量,每个分量类型为float的3D向量。
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5123,
"count" : 3,
"type" : "SCALAR",
"max" : [ 2 ],
"min" : [ 0 ]
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 3,
"type" : "VEC3",
"max" : [ 1.0, 1.0, 0.0 ],
"min" : [ 0.0, 0.0, 0.0 ]
}
],
使用mesh.primitive属性引用accessor对象,通过索引使用几何数据:
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
当需要使用几何数据进行渲染时,渲染程序可以通过引用关系,找到需要的几何数据。通过accessor对象,渲染程序可以获取数据元素的类型以及布局方式。
asset对象
对于glTF数据格式的1.0版本,asset对象是可选的,对于之后版本的glTF数据格式,必须包含asset对象,使用version属性指定glTF数据格式的版本。下面代码表示数据使用glTF数据格式的2.0版本进行描述:
"asset" : {
"version" : "2.0"
}
asset对象还可以包含其它一些描述信息,具体可以参考asset对象的官方介绍。