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

发现看不明白,这时候就需要用BufferViews来描述数据信息了:

"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》