gltf数据结构的设计解析流程
从一个最小的gltf文件入手
css
{
"scene": 0,
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0
}
],
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
"buffers" : [
{
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
"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用于描述当前默认场景
- 通过scene记录的索引在scenes属性中找到此场景根节点对应索引
- 通过scenes中nodes也就是场景根节点索引,找到目标目标网格(这里meshs[0])
- 这时候我们已经获取到目标网格,下面需要通过mesh的基元对象的attributes获取其顶点信息数据
- 其顶点信息数据也就是POSITION记录的是一个访问器对象的索引,故我们可以通过他获取到目标访问器
- 访问器对象记录有对应bufferViews的索引
- 通过bufferViews的索引拿到目标bufferViews
- bufferViews记录着数据源的索引,还有本buffer的数据偏移、大小
- 通过bufferViews记录着数据源的索引这时候我们拿到了源数据,加上数据偏移、大小我们也就能获取到最终的那块buffer切片的数据,也即我们的顶点数据
- 结束
基础属性扫盲
scene
scene属性比较简单,一个gltf文件中可能存储了多个场景,该scene属性指向的是当前要加载的场景索引。
scenes 和 nodes
每一个场景都包含一个nodes属性,他们是场景根节点的索引,因为是图结构所以可能有多个根节点。故这里的node是一个数组
构成场景图的节点
每一个节点都可以包含一个名为children的数组,该数组包含其子节点的索引。故每一个节点都是节点层次结构上的一个元素。他们将一起组成场景图
节点局部和全局转换
每一个节点都可以有一个变化,这个变化定义了平移、旋转、缩放。这个转换将应用于附加到节点本身的所有元素以及全部子节点
节点的局部转换
节点的局部转换表达有不同形式
形式一:
如下示例中由matrix属性节点定义一个列主序的4*4矩阵
json
"node0": {
"matrix": [
2.0, 0.0, 0.0, 0.0,
0.0, 0.866, 0.5, 0.0,
0.0, -0.25, 0.433, 0.0,
10.0, 20.0, 30.0, 1.0
]
}
形式二:
节点的转换也可以使用节点的 translation 、 rotation 和 scale 属性给出
json
"node0": {
"translation": [ 10.0, 20.0, 30.0 ],
"rotation": [ 0.259, 0.0, 0.0, 0.966 ],
"scale": [ 2.0, 1.0, 0.5 ]
}
节点的全局转换
无论 JSON 文件中的表示形式如何,节点的本地转换都可以存储为 4×4 矩阵。节点的全局转换由从根到相应节点的路径上所有本地转换的乘积给出
Buffers、BufferViews、 Accessors
Buffers
缓冲区表示一个原始二进制数据块,没有固有的结构或含义。缓冲区使用其uri引用此数据源。该URI可以指向外部文件,也可以是直接在JSON文件中对二进制数据进行编码的数据URI。如下:
json
"buffers" : [
{
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
"byteLength" : 44
}
],
可以理解为它表示一个数据源,里面记录着渲染所需的数据信息
BufferViews
它代表了一个缓冲区的数据切片,这个切片通过偏移量和长度来定义
json
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : 6,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : 8,
"byteLength" : 36,
"target" : 34962
}
],
Accessors
一个访问器对象指向一个bufferView并包含了定义该bufferView中数据类型和数据布局
json
"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 ]
}
],
数据类型
访问器的数据类型由type和componentType属性编码。
type属性的值是一个字符串,指定了数据元素是标量(scalars)、向量(vectors)还是矩阵(matrices)。例如,该值可以是"SCALAR"表示标量值,"VEC3"表示3D向量,或者"MAT4"表示4×4矩阵
componentType指定了这些数据元素的组件的类型。这是一个GL常量,例如,可能是5126(FLOAT)或5123(UNSIGNED_SHORT),分别表示元素具有float或unsigned short类型的组件。
数据布局
访问器的附加属性进一步指定了数据的布局。访问器的count属性指示它由多少个数据元素组成。在上面的例子中,两个访问器的count都是3,分别代表三个三角形的索引和三个顶点。每个访问器还有一个byteOffset属性。在上面的例子中,因为每个bufferView只有一个访问器,所以它们的byteOffset都是0。但当多个访问器引用同一个bufferView时,byteOffset描述了相对于它所引用的bufferView的起始位置,访问器的数据从哪里开始。
数据对齐 (这里不需要关注)
访问器引用的数据可能会被发送到显卡进行渲染,也可以在主机端用作动画或蒙皮数据。因此,访问器的数据必须根据数据的类型进行对齐。例如,当访问器的componentType是5126(FLOAT)时,数据必须在4字节边界上对齐,因为单个浮点值包含四个字节。访问器的对齐要求涉及到它所引用的bufferView和底层的缓冲区。特别地,对齐要求如下:
- byteOffset 的 accessor 必须能被其 componentType 的大小整除。
- 访问器的 和它所引用的 byteOffset bufferView 之和 byteOffset 必须能被其 componentType 的大小整除。
数据交错 (这里不需要关注)
在单个bufferView中存储的属性数据可以以数组结构的方式存储,例如,单个bufferView可以以交错方式包含顶点位置和顶点法线的数据。在这种情况下,访问器的byteOffset定义了相应属性的第一个相关数据元素的起始位置,而bufferView定义了一个额外的byteStride属性。这是其访问器的一个元素的起始位置与下一个元素的起始位置之间的字节偏移量
数据内容(这里不需要关注)
访问器还包含min和max属性,它们总结了数据的内容。它们是访问器中包含的所有数据元素的分量最小和最大值。对于顶点位置来说,min和max属性定义了一个物体的边界框。这对于优先下载或可见性检测非常有用
稀疏访问器(这里不需要关注)
2.0版本引入,数据的特殊表达形式
简单动画
先直接看示例:
css
{
"scene": 0,
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0,
"rotation" : [ 0.0, 0.0, 0.0, 1.0 ]
}
],
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
"animations": [
{
"samplers" : [
{
"input" : 2,
"interpolation" : "LINEAR",
"output" : 3
}
],
"channels" : [ {
"sampler" : 0,
"target" : {
"node" : 0,
"path" : "rotation"
}
} ]
}
],
"buffers" : [
{
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
"byteLength" : 44
},
{
"uri" : "data:application/octet-stream;base64,AAAAAAAAgD4AAAA/AABAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAPT9ND/0/TS/AAAAAAAAAAAAAAAAAACAPw==",
"byteLength" : 100
}
],
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : 6,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : 8,
"byteLength" : 36,
"target" : 34962
},
{
"buffer" : 1,
"byteOffset" : 0,
"byteLength" : 100
}
],
"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 ]
},
{
"bufferView" : 2,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 5,
"type" : "SCALAR",
"max" : [ 1.0 ],
"min" : [ 0.0 ]
},
{
"bufferView" : 2,
"byteOffset" : 20,
"componentType" : 5126,
"count" : 5,
"type" : "VEC4",
"max" : [ 0.0, 0.0, 1.0, 1.0 ],
"min" : [ 0.0, 0.0, 0.0, -0.707 ]
}
],
"asset" : {
"version" : "2.0"
}
}
核心定义,示例中的唯一节点现在具有属性 rotation 。这是一个数组,其中包含描述旋转的四元数的四个浮点值:
json
"nodes" : [
{
"mesh" : 0,
"rotation" : [ 0.0, 0.0, 0.0, 1.0 ]
}
],
动画数据描述
新增的buffers、bufferViews、accessors
erlang
"buffers" : [
...
{
"uri" : "data:application/octet-stream;base64,AAAAAAAAgD4AAAA/AABAPwAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAD0/TQ/9P00PwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAPT9ND/0/TS/AAAAAAAAAAAAAAAAAACAPw==",
"byteLength" : 100
}
],
"bufferViews" : [
...
{
"buffer" : 1,
"byteOffset" : 0,
"byteLength" : 100
}
],
动画数据的accessor对象
erlang
"accessors" : [
...
{
"bufferView" : 2,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 5,
"type" : "SCALAR",
"max" : [ 1.0 ],
"min" : [ 0.0 ]
},
{
"bufferView" : 2,
"byteOffset" : 20,
"componentType" : 5126,
"count" : 5,
"type" : "VEC4",
"max" : [ 0.0, 0.0, 1.0, 1.0 ],
"min" : [ 0.0, 0.0, 0.0, -0.707 ]
}
],
这里添加了两个新的accessor对象,用于描述如何解释动画数据,
- 第一个访问器记录了5个动画关键帧的时间
- 第二个是关键帧对象对应的旋转,以四元数的形式给出。
其对应关系如下:
times ****accessor ****时代访问器 | rotations ****accessor ****旋转访问器 | Meaning ****意义 |
---|---|---|
0.0 | (0.0, 0.0, 0.0, 1.0 ) | At 0.0 seconds, the triangle has a rotation of 0 degrees 在 0.0 秒处,三角形的旋转为 0 度 |
0.25 | (0.0, 0.0, 0.707, 0.707) | At 0.25 seconds, it has a rotation of 90 degrees around the z-axis 在 0.25 秒处,它绕 z 轴旋转 90 度 |
0.5 | (0.0, 0.0, 1.0, 0.0) | At 0.5 seconds, it has a rotation of 180 degrees around the z-axis 在 0.5 秒处,它绕 z 轴旋转 180 度 |
0.75 | (0.0, 0.0, 0.707, -0.707) | At 0.75 seconds, it has a rotation of 270 (= -90) degrees around the z-axis 在 0.75 秒处,它绕 z 轴旋转 270 (= -90) 度 |
1.0 | (0.0, 0.0, 0.0, 1.0) | At 1.0 seconds, it has a rotation of 360 (= 0) degrees around the z-axis 在 1.0 秒处,它绕 z 轴旋转 360 (= 0) 度 |
新增animation属性
实际添加动画的部分,顶级 animations 数组包含单个 animation 对象。它由两个元素组成:
- samplers:描述 动画数据来源
- channels: 将动画数据的"源"连接到"目标"。
json
"animations": [
{
"samplers" : [
{
"input" : 2, // 访问器索引
"interpolation" : "LINEAR",
"output" : 3 // 访问器索引
}
],
"channels" : [ {
"sampler" : 0,
"target" : {
"node" : 0,
"path" : "rotation"
}
} ]
}
],
在上面的例子中,有一个samplers,每一个samplers定义了一个输入和输出,它们都指向访问器对象。
- input, 时间数据访问器
- output,旋转数据访问器
此外,这里还定义了一个插值类型LINEAR
还有个channels属性,上面说了。它的主要作用是将动画数据的"源"连接到"目标"。
故:它里面的sampler就是动画数据的来源,动画的目标被编码在target属性中。他包含一个node,指的是应该做动画的节点。实际控制的节点属性在path属性表达
如上面的例子:含义就是说给node索引为0的节点的rotation属性进行动画化
简单网格
一个网格(mesh)代表了在场景中出现的几何对象。在开头glTF文件中已经展示了一个网格的示例。这个示例将一个单独的网格附加到一个单独的节点,并且该网格由一个单独的mesh.primitive组成,其中只包含了一个单独的属性------即顶点位置的属性。但通常,网格的基元(primitives)将包含更多的属性。例如,这些属性可能是顶点法线或纹理坐标等。
css
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1,
"NORMAL" : 2
},
"indices" : 0
} ]
}
],
primitives: 每个网格包含一个mesh.primitive对象数组。这些网格基元对象是一个较大对象的较小部分或构建模块。一个网格基元总结了关于对象相应部分如何渲染的所有信息
- attributes 用于定义网格的几何数据
-
- POSITION 顶点
- NORMAL 法线
材质
给上面的例子增加材质数据
css
{
"scene": 0,
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0
}
],
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0,
"material" : 0
} ]
}
],
"buffers" : [
{
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
"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 ]
}
],
"materials" : [
{
"pbrMetallicRoughness": {
"baseColorFactor": [ 1.000, 0.766, 0.336, 1.0 ],
"metallicFactor": 0.5,
"roughnessFactor": 0.1
}
}
],
"asset" : {
"version" : "2.0"
}
}
这里对比可以看到,在顶层多了一个materials。这里面记录的就是材质相关的信息(这里我们不必关系里面具体属性的含义)
json
"materials" : [
{
"pbrMetallicRoughness": {
"baseColorFactor": [ 1.000, 0.766, 0.336, 1.0 ],
"metallicFactor": 0.5,
"roughnessFactor": 0.1
}
}
],
材质与网格对象的绑定
css
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0,
"material" : 0
} ]
}
增加纹理数据,在json数据的顶层再添加三个属性如下
yaml
"textures": [
{
"source": 0,
"sampler": 0
}
],
"images": [
{
"uri": "testTexture.png"
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9987,
"wrapS": 33648,
"wrapT": 33648
}
],
每一个texture对象都有两个核心属性
- source
- sampler
纹理与材质的绑定
json
"materials" : [ {
"pbrMetallicRoughness" : {
"baseColorTexture" : {
"index" : 0
},
"metallicFactor" : 0.0,
"roughnessFactor" : 1.0
}
} ],