glTF的核心是一个JSON文件,这个文件描述了场景包含3D模型的结构和组成。文件顶层元素有:

  • scenes,nodes:scene的基本结构
  • cameras:观测场景的配置
  • meshes:3D对象的几何
  • buffers, bufferViews, accessors:数据引用和数据布局的说明
  • materials:定义对象如何被渲染
  • textures, images, samplers:对象表面外观
  • skins:顶点蒙皮的信息
  • animations:动画

这些元素被包含在数组中,对象之间的关系通过索引来建立。
也可以将所有资产存储在单个二进制文件中(.glb)。这种情况下JSON数据被存储为字符串,后面跟缓冲区和图像的二进制数据。

概念关系

顶层元素之间的概念关系如图:

3D模型查看与格式转换教程配图
顶层元素关系图

二进制数据引用

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索引数组

3D模型查看与格式转换教程配图
scenes,nodes
3D模型查看与格式转换教程配图
nodes

一个node可以包含一个local transform. 可以作为列主序矩阵( column-major matrix)数组给出,也可以是具有单独的translationrotationscale属性,其中rotation以四元数的形式给出。
局部变换矩阵可以由以下公式计算 
节点的全局变换由 从root到相应节点的路径上的所有局部变换的乘积 得到

*列主序矩阵: 以列为优先单位,在内存中逐列存储,比如平移矩阵以列主序方式存储:
3D模型查看与格式转换教程配图
平移矩阵以列主序方式存储


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

3D模型查看与格式转换教程配图

node的平移、旋转和缩放属性也可能应用于一个animation:然后这个动画描述一个属性如何随时间变化。 关联的对象将相应地移动,可以是模拟物体移动,也可以是相机飞行。
nodes也可以用于顶点蒙皮:顶点的层次结构可以定义动画角色的骨架。节点指向一个meshskinskin包括mesh基于当前骨架姿势形变的更多信息。

meshes

meshes可能包含多个网格图元(mesh primitive)。这些图元是指渲染mesh所需的几何数据。Primitive 是 glTF 数据规范中最小的图形单位。

3D模型查看与格式转换教程配图

参数

  • 渲染模式(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)定义了缓冲数据的数据类型和布局。

3D模型查看与格式转换教程配图
3D模型查看与格式转换教程配图


缓冲(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对象可以通过稀疏访问器,共享同一份几何数据。

3D模型查看与格式转换教程配图

materials(材质)

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

3D模型查看与格式转换教程配图
金属粗糙度模型
  • metallic:用于描述3D对象表面的反射表现和金属表面的反射表现的相似度。
  • roughness:用于描述3D对象表面对散射光的影响。

pbrMetallicRoughness对象指定了基于metallic-roughness模型的材质属性:

  • baseColorFactor属性包含了红,绿,蓝和alpha成分,构成了材质的基本颜色
  • metallicFactor属性用于指定材质的反射情况与金属的相似度
  • roughnessFactor属性用于指定材质粗糙度

除此之外,材质中还包含影响外观的其他属性:法线贴图(normalTexture),遮挡贴图(occlusionTexture),自发光贴图(emissiveTexture)
数据引用结构:

3D模型查看与格式转换教程配图
贴图数据引用关系

cameras(相机)

每个节点(node)都可以引用一个glTF资源中定义的camera。

glTF资源可以定义两种类型的相机:透视投影(perspective)相机正交投影(orthographic)相机

3D模型查看与格式转换教程配图
camera

type属性值决定了camera对象是否包含有perspective对象或orthographic对象。他们实际包含了视椎体的参数信息。

可以认为,glTF资源的JSON文件中定义的camera对象实际上是一个模板对象,在需要的时候,实例化出一个camera对象来给node对象使用。
该实例的相机变换矩阵受节点对象的全局变换影响

textures, images, samplers(纹理,图像,采样器)

使用纹理(textures)可以更加精确的描述3D对象的基本颜色。一个glTF资源文件可以定义多个texture对象,每个texture对象可以用于多个材质(materials)对象。

3D模型查看与格式转换教程配图

glTF资源的JSON文件可以包含一个textures数组对象,用于定义texture对象。包含一个images数组对象,用于定义image对象。包含一个samplers数组对象,用于定义sampler对象。
引用关系示例如左图。
采样器samplers中设置了纹理采样参数,比如过滤、repeat、缩放等

skins(蒙皮)

使用顶点蒙皮(vertex skinning),可以根据当前pose让网格的顶点受到关节和骨架的影响.
skin对象包含两个属性:joints 和 inverseBindMatricesjoints包含了关节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_0WEIGHTS_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];
  1. Undo the skin's world-to-node transformations, if any.
  2. Apply the joint's world-to-joint transformations.
  3. 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;
3D模型查看与格式转换教程配图

animations(动画)

动画描述了节点translationrotationscale 属性随时间的变化。

3D模型查看与格式转换教程配图

参数
channels:数组结构,给定动画作用节点(node)及属性(path)、所引用的采样器
samplers给出了关键帧时间序列(input)和动画属性数据(output),以及两个关键帧之间的插值方式

path取值:*"translation", "rotation","scale" or "weights"
采样器根据采样参数输出的数据会被写入到动画通道(channel)的path所指定属性中