1-glTF文件简介
在看完GAMES104动画系统那一部分后,我也想写一个简单的动画系统,又要开一个坑了!本文将简要介绍glTF文件的格式。
glTF文件简介
glTF文件格式为高效读取3D场景和模型而被发明,它的格式是公开标准,支持外部文件和内嵌Base64编码的数据,且容易拓展新特性。
组成部分
接下来看看glTF文件格式的组成:
- 场景Scene:glTF文件的顶层元素是场景,是场景层次结构的根节点。一个glTF文件可能包含多个场景,并标明默认场景。
- 节点Node:节点是模型的最小单元,glTF文件中的节点通常描述模型中的一部分,例如人模型的腿、机械模型的一个齿轮等。它包含许多当前节点信息,如位置、SRT变换、和它相连的子节点等。
- 相机Camera:定义相机是如何看向模型的,包括相机的种类,正交/透视投影等。
- 蒙皮Skin:定义顶点蒙皮动画的相关参数,可以附加到节点上,被访问器Accessor获取。
- 网格Mesh:定义节点的几何信息,可由访问器Accessor获取相关信息。
- 动画Animation:定义了节点在一段时间内的变化信息,例如SRT变换等。
- 缓冲Buffer:是一块二进制数据,用于内嵌数据或指向外部文件。需要用BufferView或者Accessor翻译它。
- BufferView:首先需要它来读取缓冲,它将Buffer按信息种类切片,提供Accessor查看。
- 访问器Accessor:可看作网格、蒙皮、动画等数据的抽象。它定义了数据的类型、在BufferView中的排列和对齐方式。
- 材质Material:材质包含模型的外观,glTF文件格式使用PBR材质。它包括用于主表面颜色的基色,反射用的金属度和漫反射用的粗糙度。还有如自发光、透明度等参数。也会指向纹理。
- 纹理Texture:允许glTF模型拥有更自然的外观,通常指向一个纹理和采样器对象,被材质所使用。
- 图像Image:指向图片文件,包括内嵌数据、JPEG和PNG等格式的图片。
- 采样器Sampler:采样器定义如何采样一张纹理,例如之前OpenGL介绍过的。
探索样例文件
glTF格式文件使用JSON存储数据,此外还有更节省空间的二进制格式。接下来看看一个标准三角形的gltf文件包含什么:
场景
文件首先要定义场景:
{
"scene": 0,
"scenes": [
{
"nodes": [0]
}
],
...
scene
描述了文件中的默认场景索引,用于创建读取文件后默认开始的场景。scenes
则描述不同场景中包含的节点索引信息。
节点和网格
接下来定义节点:
"nodes" : [
{
"mesh": 0
}
],
...
目前只定义了节点0,节点0内只包含了网格0。该节点没有子节点,有的话会定义在单独的孩子节点块,然后是定义SRT变换。此外还有一个叫做name
的定义区域,该区域定义了节点的可读名字或描述。
网格则定义如下:
"meshes" : [
{
"primitives": [{
"attributes" : {
"POSITION" : 1
},
"indices" : 0
}]
}
],
...
图元描述了网格内的元素,属性则定义了存储在访问器Accessor中的数据,这里将要使用在访问器Accessor中索引为1的顶点数据为位置。除此之外也能用几何索引来渲染多边形,就像IBO那样。
缓冲
缓冲的定义如下:
"buffers" :[
{
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
"byteLength" : 44
}
]
缓冲包含一个数据URI,和base64编码的内嵌数据,以及数据的长度。将base64数据转码,用十六进制来读取如下:
00 00 01 00 02 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 80 3f
00 00 00 00 00 00 00 00
00 00 00 00 00 00 80 3f
00 00 00 00
发现看不明白,这时候就需要用BufferView
s来描述数据信息了:
"bufferViews": [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : 6,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : 8,
"byteLength" :36,
"target" : 34962
}
],
这两个BufferView
提供了缓冲0的两种视角。前者从位置0开始,长度6字节;后者从位置8开始,长度36个字节。而target
则是glTF定义的魔数,例如34963是ELEMENT_ARRAY_BUFFER
;34962是ARRAY_BUFFER
。那么两段buffer就能分开了:
00 00 01 00 02 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 80 3f
00 00 00 00 00 00 00 00
00 00 00 00 00 00 80 3f
00 00 00 00
访问器
访问器则具体描述了上面的数据应该怎么用:
"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 ]
}
],
...
首先看看Accessor0,它将使用BufferVIew0中的数据,类型5123是UNSIGNED_SHORT
,每个标量2字节。然后是Accessor1,它将使用BufferView1中的数据,类型5126是FLOAT
,4字节。
那么上面的数据就能转换如下:
0 1 2
0.0 0.0 0.0
1.0 0.0 0.0
0.0 1.0 0.0
Asset元素
Asset元素则提供了文件的版本定义:
"asset" : {
"version" : "2.0"
}
}
此外还有诸如版权等定义。
参考资料
- LearnOpenGL - Skeletal Animation
- 《C++ Game Animation Programming》