【译】glTF教程:缓冲区、缓冲视图和访问器(五)
缓冲区、缓冲视图和访问器
buffer,bufferView以及accessor对象已经在示例:最小的glTF文件部分提到。本节将更详细地解释这些概念。
Buffers缓冲区
buffer
表示原始二进制数据块,没有固有的结构或含义。该数据通过其引用uri由缓冲区使用。此URI可以指向外部文件,也可以是直接在JSON文件中编码二进制数据的基本glTF结构。示例:最小的glTF文件包含的一个buffer例子,具有44个字节的数据,就是在数据URI编码:
"buffers" : [
{
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
"byteLength" : 44
}
],
buffer数据的一部分可能必须作为顶点属性或索引,或者可能包含皮肤信息或动画关键帧的数据传递给渲染器。为了能够使用此数据,需要有关此数据的结构和类型的其他信息。
BufferViews
buffer构造数据的第一步是使用bufferView对象。一个bufferView
表示一个缓冲区的数据的“切片”。使用偏移量和长度(以字节为单位)定义此切片。示例:最小的glTF文件定义了两个bufferView对象:
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : 6,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : 8,
"byteLength" : 36,
"target" : 34962
}
],
第一个bufferView是指缓冲区数据的前6个字节。第二个引用缓冲区的36个字节,偏移量为8个字节,如下图所示:
以浅灰色显示的字节是正确对齐访问器所需的填充字节,如下所述。
每个bufferView附加包含一个target属性。随后渲染器可以使用此属性来分类缓冲区视图所引用的数据的类型或性质。target可以是一个常量指示数据是用于顶点属性(34962,表示ARRAY_BUFFER),或者该数据被用于顶点索引(34963,表示ELEMENT_ARRAY_BUFFER)。
此时,buffer数据已被分成多个部分,每个部分由一个部分描述bufferView。但是要真正在渲染器中使用此数据,需要有关数据类型和布局的其他信息。
accessor访问器
一个accessor
对象是指一种bufferView,它包含定义本数据的类型和布局的属性。
数据类型
访问器的数据类型在type和componentType属性中编码。type属性的值是一个字符串,它指定数据元素是标量,向量还是矩阵。例如,该值可以是”SCALAR”-标量值,”VEC3”-3D矢量或”MAT4”-4×4矩阵。
在componentType指定了这些数据元素的组分的类型。例如GL常数可以是5126(FLOAT)或5123(UNSIGNED_SHORT),以分别指示元素具有float或unsigned short成分。
可以使用这些不同的属性组合来描述任意数据类型。例如,示例:最小的glTF文件包含两个访问器:
"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 ]
}
],
第一个访问器引用索引0的bufferView,它定义buffer数据包含索引的部分。它type是”SCALAR”,它componentType是5123(UNSIGNED_SHORT)。这意味着索引存储为标无符号短整型unsigned short值。
第二个访问器引用索引1的bufferView,它定义buffer数据包含顶点属性的部分- 特别是顶点位置。它的类型是”VEC3”,它componentType是 5126(FLOAT)。因此,该访问器描述了具有浮点值的3D矢量。
数据布局
accessor其他属性进一步指定数据的布局。accessor的count属性表示它包含多少数据元素。在上面的示例中,计数为3表示两个accessor的计数分别代表三个索引和三角形的三个顶点。每个accessor也有一个byteOffset属性。上例中两个accessor都是0,因为每个accessor只有一个bufferView。但是当多个accessor引用相同时bufferView,则byteOffset描述accessor相对于bufferView它所引用的数据的开始位置。
数据对齐
accessor引用的数据可以被发送到图形卡用于渲染,或者在主机用作动画或蒙皮数据。因此,accessor必须根据数据的类型对齐数据。例如,当一个accessor的componentTypea为5126(FLOAT)时,数据必须以4字节边界对齐,因为单个float值由4个字节组成。accessor这种对齐要求指的是它的bufferView和它的基础buffer。特别的,对准要求如下:
accessor的偏移值必须能被它的componentType大小整除。 accessor的偏移值及其指向的bufferView的偏移值之和必须能被它的componentType大小整除。 在上面的例子中,索引为1的bufferView(它指的是顶点属性)的偏移值为8,以便对齐accessor中顶点数据的4字节边界位置。Buffer的6和7字节因此填充不相关数据。
图5c示出了如何使用bufferView对象来构造buffer的原始数据,并且使用accessor对象来扩充数据类型信息。
数据交织
存储在单个bufferView中的属性数据可以存储为结构阵列。例如单个bufferView可以以交错的方式包含顶点位置和顶点法线的数据。在这种情况下,accessor的偏移值定义了相应属性的第一个相关数据元素的开始,并bufferView额外定义了一个byteStride属性。这是accessor中一个元素起始到下一个元素起始之间的字节数。图5d为交错位置和普通属性如何存储在bufferView中的一个示例。
数据内容
accessor还包含汇总其数据内容的min和max属性。它表示accessor访问器中所有数据元素的最小值和最大值。在顶点位置accessor中,min和max属性定义了对象的边界框bounding box。这对于下载优先级或可见性检测非常有用。通常,此信息对于渲染器存储和处理量化数据(在运行时非量化)也很有用,但此量化的详细信息超出了本教程的范围。
稀疏accessor
在2.0版本中,glTF引入了稀疏accessor访问器的概念。这是数据的特殊表示,允许将仅具有少量不同条目的多个数据块极大压缩存储。例如,当存了包含顶点位置的几何数据时,此几何数据可用于多个对象。这可以通过两个对象引用相同的accessor来实现。如果两个对象的顶点位置大部分相同且仅在几个顶点上不同,则不必将整个几何数据存储两次。相反,可以仅存储一次数据,并使用稀疏存取器仅存储第二个对象不同的顶点位置。
以下是嵌入式表示中的完整glTF结构,它显示了稀疏存取器 sparse accessors的示例:
{
"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"
}
}
渲染此结构的结果如图5e所示:
该示例包含两个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对象本身定义了将受替换影响的元素个数。sparse.indices属性指的是包含将被替换元素索引的bufferView。sparse.values指bufferView包含实际数据。
在该示例中,原始几何数据存储在索引1的bufferViewwith中。它描述了一个矩形的顶点数组。sparse.indices指向索引为2的bufferView,其中包含[8, 10, 12]的索引。sparse.values指索引为3的bufferView,其中包含新的顶点位置,即[(1,2,0), (3,3,0), (5,4,0)]。应用相应取代的效果如图5f所示。