Buffers
一个buffer表示一个原始的二进制数据块,这个数据块没有固有的结构或意义。buffer使用其URI引用数据,此URI可能指向一个外部文件,也可以直接在JSON文件中使用编码的二进制数据的data URI。The minimal glTF文件的例子中包括了一个buffer,该buffer具有44个字节(byte)的数据,并用data URI编码。
"buffers" : [
{
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
"byteLength" : 44
}
],
Image 1: The buffer data, consisting of 44 bytes.
buffer中数据一部分数据可能被作为顶点属性、索引、包含skinning information、animation key frames等信息传递给渲染器(renders)。为了能使用此数据,需要有关此数据的结构(structure)和类型(type)的其他信息。
BufferViews
从buffer构造数据的第一步是使用bufferView对象。bufferView表示一个缓冲区数据的一个“切片”。该切片使用偏移量(offset)和长度(length,以字节byte为单位)定义。The minimal glTF file定义了两个bufferView对象。
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : 6,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : 8,
"byteLength" : 36,
"target" : 34962
}
],
第一个bufferView引用buffer数据的前六个字节,第二个bufferView引用buffer数据的36个字节,偏移量为8个字节(见图2)。
Image 2: The buffer views, referring to parts of the buffer.
浅灰色显示的字节是正确对其访问器所需的填充字节:
每一个bufferView还额外包含一个target属性。渲染器往后可以使用该属性来对bufferView引用的buffer数据的类型(type)或性质(nature)进行分类。target是一个常量,该常量只是该数据用于顶点属性(vertex attributes,34962,代表ARRAY_BUFFER),或者该数据用于顶点索引(vertex indices, 34963,代表ELEMENT_ARRAY_BUFFER)。
此时,buffer中的数据以被分成多个部分,每个部分由一个bufferView描述。但是,为了能在渲染器中真正使用此数据,还需要有关数据类型和布局的其他信息。
Accessors
一个accessor引用一个bufferView,且包含定义该bufferView的数据类型和布局的属性。
Data type
accessor数据的类型编码在type和componentType属性中。type属性的值是一个字符串,用于指定数据元素是标量(scalars,SCALAR)、向量(vectors,VEC3)还是矩阵(matrices,MAT4)。对于标量值,该值为SCALAR。对于3D向量,该值为VEC3。对于4×4矩阵,该值可以是MAT4。
componentType指定这些数据元素的组件的类型。这是一个GL常量,可以为5126(FLOAT)或5123(UNSIGNED_SHORT),以知识元素分别具有float或unsigned short分量。
这些属性的不同组合可用于描述任意的数据类型。例如:the minimal glTF file包含两个访问器:
"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 ]
}
],
第一个accessor引用索引为0的bufferView,定义了包含索引的buffer数据部分。其类型为“SCALAR”,它的componentType为5123(UNSIGNED)SHORT),这意味着索引存储为scalar unsigned_short。
第二个accessor引用索引为1的bufferView,它定义了buffer数据中包含顶点属性(尤其是顶点位置)的部分。其类型为“VEC3”,组成类型为5126(FLOAT)。因此,该accessor描述了具有浮点分量的3D矢量。
Data layout
accessor第其他属性进一步指定数据的布局(layout)。count属性指示其包含多少个数据元素。在上面的例子中,每个accessor的count属性都是3,分别代表三角形的三个索引和三个顶点。每个accessor还具有一个byteOffset属性。在上面的例子中,两个accessor的byteOffset均为0,因为每个bufferView只被一个accessor引用。但是,当多个accessor引用同一个bufferView时,byteOffset将描述accessor的数据从bufferView中的何处开始。
Data alignment
一个accessor引用的数据可以被发送到显卡(graohics card)以进行渲染,也可以在主机端(host sides)被用作动画(animation)或蒙皮(skinning)数据。因此,accessor中的数据必须基于数据类型进行对齐。例如,当accessor的componentType为5126(FLOAT)时,数据必须在4字节的边界处对齐,因为单个浮点值由四个字节组成。accessor的这种对齐需求是指bufferView和buffer。特别地,对齐的要求如下:
- accessor的byteOffset必须可被其componentType的大小整除。
- accessor和它引用的bufferView的byteOffset的值之和必须可以被componentType的大小整除。
在上面的事例中,索引为1的bufferView的byteOffset为8,以便将顶点位置的accessor数据对齐到4字节边界。因此,buffer中的字节6、7是不携带相关数据的填充字节。
图3说明了如何使用bufferView对象构造buffer的原始数据,以及如何使用accessor对象来增加数据类型信息:
Image 3: The accessors defining how to interpret the data of the buffer views.
Data interleaving
存储在单个bufferView中的属性数据可以存储为结构数组(Array-Of-Structures)。单个bufferView可以以交错(interleaved)的方式包含顶点位置和顶点法线的数据。在这种情况下,accessor的byteOffset定义相应属性的第一个相关数据元素的开头,bufferView定义一个不加的byteStride属性。这是其accessor的一个元素的开始于下一个元素开始之间的字节数。图4是一个在bufferView中以交错方式存储位置和法线属性的事例。
Image4: Interleaved accessors inone buffer view.
Data contents
accessor还包括min和max属性,汇总其数据内容的属性。它们是accessor中包含的所有数据元素的按组件的最小值和最大值。因此,在顶点位置的情况下,min和max属性定义对象的边界框。这对于优先下载或可见性(visibility)监测很有用。通常来说,此信息对于存储和处理渲染器在运行时反量化的量化数据也很有用,但是此量化的详细信息不在此教程的讨论范围内。
Sparse accessors
在2.0版本中,glTF引入了稀疏访问器(sparse accessors)的概念。这是数据的一种特殊表示形式,允许非常紧凑地存储只有几个不同条目(entries)的多个数据块。例如,当存在包含顶点位置的几何数据时,该几何数据可用于多个对象。这可以通过两个对象引用相同的accessors来实现。如两个对象的顶点位置大部分相同,仅几个顶点不同时,不需要将整个几何数据存储两次。相反,可以只存储一次数据,并使用稀疏访问器仅存储第二个对象的不同顶点位置。
下面是一个完整的glTF asset,以嵌入式表示形式,显示了稀疏访问器的实例:
{
"scenes" : [ {
"nodes" : [ 0 ]
} ],
"nodes" : [ {
"mesh" : 0
} ],
"meshes" : [ {
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
} ],
"buffers" : [ {
"uri" : "data:application/gltf-buffer;base64,AAAIAAcAAAABAAgAAQAJAAgAAQACAAkAAgAKAAkAAgADAAoAAwALAAoAAwAEAAsABAAMAAsABAAFAAwABQANAAwABQAGAA0AAAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAQAAAAAAAAAAAAABAQAAAAAAAAAAAAACAQAAAAAAAAAAAAACgQAAAAAAAAAAAAADAQAAAAAAAAAAAAAAAAAAAgD8AAAAAAACAPwAAgD8AAAAAAAAAQAAAgD8AAAAAAABAQAAAgD8AAAAAAACAQAAAgD8AAAAAAACgQAAAgD8AAAAAAADAQAAAgD8AAAAACAAKAAwAAAAAAIA/AAAAQAAAAAAAAEBAAABAQAAAAAAAAKBAAACAQAAAAAA=",
"byteLength" : 284
} ],
"bufferViews" : [ {
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : 72,
"target" : 34963
}, {
"buffer" : 0,
"byteOffset" : 72,
"byteLength" : 168
}, {
"buffer" : 0,
"byteOffset" : 240,
"byteLength" : 6
}, {
"buffer" : 0,
"byteOffset" : 248,
"byteLength" : 36
} ],
"accessors" : [ {
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5123,
"count" : 36,
"type" : "SCALAR",
"max" : [ 13 ],
"min" : [ 0 ]
}, {
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 14,
"type" : "VEC3",
"max" : [ 6.0, 4.0, 0.0 ],
"min" : [ 0.0, 0.0, 0.0 ],
"sparse" : {
"count" : 3,
"indices" : {
"bufferView" : 2,
"byteOffset" : 0,
"componentType" : 5123
},
"values" : {
"bufferView" : 3,
"byteOffset" : 0
}
}
} ],
"asset" : {
"version" : "2.0"
}
}
渲染的结果见图5所示:
Image 5: The result of rendering the simple sparse accessor asset.
这个例子中包含两个accessor,一个是mesh的索引,另一个是顶点位置(vertex positions)。引用顶点位置的那个accessor定义了一个额外的accessor.sparse属性,该属性包含有关应该用稀疏数据替换的信息:
"accessors" : [
...
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 14,
"type" : "VEC3",
"max" : [ 6.0, 4.0, 0.0 ],
"min" : [ 0.0, 0.0, 0.0 ],
"sparse" : {
"count" : 3,
"indices" : {
"bufferView" : 2,
"byteOffset" : 0,
"componentType" : 5123
},
"values" : {
"bufferView" : 3,
"byteOffset" : 0
}
}
} ],
这个sparse对象本身定义了将受替换影响的元素数量(count of elements)。sparse.indices属性引用的bufferView包含将被替换的元素的索引,sparse.values引用包含实际数据的bufferView。
在这个例子中,原始几何数据存储在索引为1的bufferView中,它描述了顶点的矩形阵列。sparse.indices引用具有索引2的bufferView,其中包含索引[8, 10, 12]。sparse.values引用索引为3的bufferView,其中包含新的顶点位置,即[(1, 2, 0), (3, 3, 0), (5, 4, 0)]。图6中显示了相应的替换效果。
Image 6: The substitution that is done with the sparse accessor.