glTF的核心是一个JSON文件,这个文件描述了场景包含3D模型的结构和组成。文件顶层元素有:
scenes,nodes:scene的基本结构cameras:观测场景的配置meshes:3D对象的几何buffers, bufferViews, accessors:数据引用和数据布局的说明materials:定义对象如何被渲染textures, images, samplers:对象表面外观skins:顶点蒙皮的信息animations:动画
这些元素被包含在数组中,对象之间的关系通过索引来建立。
也可以将所有资产存储在单个二进制文件中(.glb)。这种情况下JSON数据被存储为字符串,后面跟缓冲区和图像的二进制数据。
概念关系
顶层元素之间的概念关系如图:

二进制数据引用
glTF 资产的图像和buffers可能引用外部文件,这些外部文件包含例如渲染3D内容所需数据:
// buffers指的是包含几何或动画数据的二进制文件(.BIN)。
"buffers": [
{
"uri": "buffer01.bin"
"byteLength": 102040,
}
],
// images是指包含模型贴图数据的图像文件(PNG、JPG ...)
"images": [
{
"uri": "image01.png"
}
],数据通过uris被引入,但是也可以直接通过data URIs包含在JSON中。
scenes,nodes
glTF JSON可能包含scenes(带有可选的默认 scene)每个场景都可以包含一个nodes索引数组。每一个nodes可以包含一个它的children的nodes索引数组


一个node可以包含一个local transform. 可以作为列主序矩阵( column-major matrix)数组给出,也可以是具有单独的translation、rotation、scale属性,其中rotation以四元数的形式给出。
局部变换矩阵可以由以下公式计算
节点的全局变换由 从root到相应节点的路径上的所有局部变换的乘积 得到
*列主序矩阵: 以列为优先单位,在内存中逐列存储,比如平移矩阵以列主序方式存储:

每个节点可以引用一个mesh或一个camera,使用指向meshes和cameras数组的索引,然后这些元素被附加到这些节点。
在渲染过程中,这些元素的实例被创建,并且按照节点的全局变换进行变换。

node的平移、旋转和缩放属性也可能应用于一个animation:然后这个动画描述一个属性如何随时间变化。 关联的对象将相应地移动,可以是模拟物体移动,也可以是相机飞行。nodes也可以用于顶点蒙皮:顶点的层次结构可以定义动画角色的骨架。节点指向一个mesh或skin。skin包括mesh基于当前骨架姿势形变的更多信息。
meshes
meshes可能包含多个网格图元(mesh primitive)。这些图元是指渲染mesh所需的几何数据。Primitive 是 glTF 数据规范中最小的图形单位。

参数
- 渲染模式(
mode):一个常量,指示是否应将其渲染为点(POINTS)、线(LINES)或三角形(TRIANGLES)。 - 索引(
indices):primitive对象引用的几何数据可以是有索引的,也可以是无索引的。索引数据通过indices属性指定accessor对象来指定。
例子中indices为0,表示使用第一个accessor来解析该mesh
- 属性(
attributes):描述mesh对象所使用的几何数据,通过accessor的索引给出。
示例中,分别引用了索引为1的顶点位置信息accessor对象和索引为2的顶点法线accessor对象
- 材质(
material):通过索引引用一个用于渲染的material对象。
默认的material为50%程度的灰色
- 变形信息(
targets):targets对象数组指定mesh的变形信息 - 权重(
weights):权重信息决定了附加在原始几何数据上的变形程度。
animation对象可以动态的修改mesh的权重信息,从而产生变形动画,例如,对角色的不同面部表情进行建模:可以使用动画修改权重,以在几何体的不同状态之间进行插值。
*mode属性值参考官方规范,默认为4(TRIANGLES)
*attributes参数:顶点POSITION、顶点法线NORMAL、uv坐标TEXCOORD_0、TANGENT
buffers, bufferViews, accessors(缓冲,缓冲视图,访问器)
缓冲(buffers)包括了3D模型几何、动画和蒙皮数据。缓冲视图(bufferViews)为buffers提供结构化信息。访问器(accessors)定义了缓冲数据的数据类型和布局。


缓冲(buffers): 缓冲的数据通过URI引用是一个给定字节长度的二进制数据块。可以引用外部文件也可以直接使用数据。
使用缓冲数据,需要额外的信息来描述buffers中的数据结构和类型
缓冲视图(bufferViews):一个bufferView对象代表一个buffer的部分数据。这一部分数据的范围通过偏移量(byteOffset)和长度(byteLength)来表示。
多个访问器在bufferView中交错时,将会有一个byteStride属性,表示访问器的一个元素的开始与下一个元素的开始之间有多少字节。target属性的值表示数据使用方式
例子中的bufferView对象引用了索引为0的buffer对象的偏移量4开始的28个字节的buffer对象数据
访问器(accessors**):数据类型 + 布局
例子中accessor对象引用了索引为0的bufferView,从偏移量4开始,【数据类型】数据分量基础类型为5126(FLOAT)数据类型为VEC2(2D向量)的数据。【布局】count属性为2,min,max存储了所有值的范围
*target属性值_34962表示ARRAY_BUFFER(顶点buffer),34963表示ELEMENT_ARRAY_BUFFER(索引buffer)_Sparse accessors(稀疏访问器)
glTF的2.0版本引入了稀疏访问器的概念。稀疏访问器允许数据以非常紧凑的方式进行存储。多个3D对象可以通过稀疏访问器,共享同一份几何数据。

materials(材质)
每个网格图元都可以引用 glTF 资产中包含的材质。 材质根据物理材质特性描述了对象的渲染方式。为了确保更真实的渲染效果,允许使用PBR技术。
默认使用金属粗糙度模型(Metallic-Roughness-Model)表示3D对象表面物理属性

metallic:用于描述3D对象表面的反射表现和金属表面的反射表现的相似度。roughness:用于描述3D对象表面对散射光的影响。
pbrMetallicRoughness对象指定了基于metallic-roughness模型的材质属性:
- baseColorFactor属性包含了红,绿,蓝和alpha成分,构成了材质的基本颜色
- metallicFactor属性用于指定材质的反射情况与金属的相似度
- roughnessFactor属性用于指定材质粗糙度
除此之外,材质中还包含影响外观的其他属性:法线贴图(normalTexture),遮挡贴图(occlusionTexture),自发光贴图(emissiveTexture)
数据引用结构:

cameras(相机)
每个节点(node)都可以引用一个glTF资源中定义的camera。
glTF资源可以定义两种类型的相机:透视投影(perspective)相机和正交投影(orthographic)相机

type属性值决定了camera对象是否包含有perspective对象或orthographic对象。他们实际包含了视椎体的参数信息。
可以认为,glTF资源的JSON文件中定义的camera对象实际上是一个模板对象,在需要的时候,实例化出一个camera对象来给node对象使用。
该实例的相机变换矩阵受节点对象的全局变换影响
textures, images, samplers(纹理,图像,采样器)
使用纹理(textures)可以更加精确的描述3D对象的基本颜色。一个glTF资源文件可以定义多个texture对象,每个texture对象可以用于多个材质(materials)对象。

glTF资源的JSON文件可以包含一个textures数组对象,用于定义texture对象。包含一个images数组对象,用于定义image对象。包含一个samplers数组对象,用于定义sampler对象。
引用关系示例如左图。
采样器samplers中设置了纹理采样参数,比如过滤、repeat、缩放等
skins(蒙皮)
使用顶点蒙皮(vertex skinning),可以根据当前pose让网格的顶点受到关节和骨架的影响.skin对象包含两个属性:joints 和 inverseBindMatrices。joints包含了关节node对象的索引;inverseBindMatrices属性引用一个accessor对象,这个accessor包含了每个关节node变换到对应关节的矩阵信息。也就是关节点node初始全局变换矩阵的逆矩阵信息。
蒙皮应用伪代码示例:
非蒙皮顶点通常会像这样计算:gl_Position = modelViewProjection * position
蒙皮顶点像这样计算:gl_Position = modelViewProjection * skinMatrix * position
关节和权重
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0,
"JOINTS_0": 1,
"WEIGHTS_0": 2
...
},
]
}
],primitives中有两个新的属性JOINTS_0和WEIGHTS_0用于顶点蒙皮。它们引用的accessor对象为网格中的每个顶点提供蒙皮信息。JOINTS_0属性数据包含对顶点产生影响的关节的索引。WEIGHTS_0引用的访问器提供每个关节影响每个顶点的权重信息。
有以上信息,可以计算出蒙皮矩阵(skinning matrix)
通常需要限制每个顶点取一部分权重,因为否则会有太多的数据。一个角色可拥有位于任何地方的15个骨骼(VR 战士1)到150-300个骨骼(许多现代游戏)。 如果你有300个骨骼,那么每个顶点需要300个权重对应300个骨骼。如果你有10000个顶点就会需要3百万个权重。 所以,大多数实时蒙皮系统限制每个顶点~4个权重。通常这是在导出器/转换器中完成的,从像blender/maya/3dsmax的3D软件包中获取数据,并对于每个顶点找到最大的四个权重并归一化这些权重。 reference
举个
Vertex 0: 0, 1, 0, 0,
Vertex 1: 0, 1, 0, 0,
Vertex 2: 0, 1, 0, 0,
Vertex 3: 0, 1, 0, 0,
Vertex 4: 0, 1, 0, 0,
Vertex 5: 0, 1, 0, 0,
Vertex 6: 0, 1, 0, 0,
Vertex 7: 0, 1, 0, 0,
Vertex 8: 0, 1, 0, 0,
Vertex 9: 0, 1, 0, 0,上面的数据表明,顶点只受索引为0和1的两个关节的影响。
Vertex 0: 1.00, 0.00, 0.0, 0.0,
Vertex 1: 1.00, 0.00, 0.0, 0.0,
Vertex 2: 0.75, 0.25, 0.0, 0.0,
Vertex 3: 0.75, 0.25, 0.0, 0.0,
Vertex 4: 0.50, 0.50, 0.0, 0.0,
Vertex 5: 0.50, 0.50, 0.0, 0.0,
Vertex 6: 0.25, 0.75, 0.0, 0.0,
Vertex 7: 0.25, 0.75, 0.0, 0.0,
Vertex 8: 0.00, 1.00, 0.0, 0.0,
Vertex 9: 0.00, 1.00, 0.0, 0.0,比如顶点6,手关节点0百分之25的影响,受关节点1百分之75的影响
计算蒙皮矩阵
蒙皮矩阵是骨骼到当前姿势的mesh顶点变换矩阵(bind pose to current pose)。最终的变换结果可以认为是使用多个关节矩阵(joint matrix)的加权变换
关节矩阵
顶点着色器是基于skin mesh node的上下文执行的,而不是骨架或关节。
jointMatrix[j] =
inverse(globalTransform) *
globalJointTransform[j] *
inverseBindMatrix[j];
- Undo the skin's world-to-node transformations, if any.
- Apply the joint's world-to-joint transformations.
- Undo the "rest" position of the joint.
蒙皮矩阵计算
...
mat4 skinMatrix =
a_weight.x * u_jointMatrix[int(a_joint.x)] +
a_weight.y * u_jointMatrix[int(a_joint.y)] +
a_weight.z * u_jointMatrix[int(a_joint.z)] +
a_weight.w * u_jointMatrix[int(a_joint.w)];
gl_Position = modelViewProjection * skinMatrix * position;

animations(动画)
动画描述了节点translation, rotation, scale 属性随时间的变化。

参数:channels:数组结构,给定动画作用节点(node)及属性(path)、所引用的采样器samplers: 给出了关键帧时间序列(input)和动画属性数据(output),以及两个关键帧之间的插值方式
path取值:*"translation", "rotation","scale" or "weights"
采样器根据采样参数输出的数据会被写入到动画通道(channel)的path所指定属性中