• HappyGIS 正式上线啦~
    • HappyGIS 现在已经支持 Markdown 格式评论~

“glTF”格式学习

三维 张明 3278次浏览 0个评论

首先说明一哈,在搞三维这一块。因为公司项目的需求,要将通用的三维数据格式(.obj)转换成(.glTF)数据格式,这是一种json数据格式,webGL直接加载此格式来渲染三维模型

下面将学习的成果跟大家分享下:

“glTF”格式学习

1.glTF简介

​  glTF的全称是GL传输格式,是一种针对GL(WebGL,OpenGL ES以及OpenGL)接口的运行时资产(asset)。在3D内容的传输和加载中,glTF通过提供一种高效,易扩展,可协作的格式,填补了3D建模工具和现代GL应用之间的空白。

2.为什么要有glTF这个格式的提出呢?

​ 虽然以前有很多3D格式,但是各种3D模型渲染程序都要处理很多种的格式。对于那些对载入格式不是那么重要的软件(不是那些类似“格式工厂”那样的软件),可以显著减少代码量,所以也有人说,最大的受益者是那些对程序大小敏感的3D Web渲染引擎,只需要很少的代码就可以顺利地载入各种模型了。此外,glTF是对近二十年来各种3D格式的总结,使用最优的数据结构,来保证最大的兼容性以及可伸缩性。glTF使用json格式进行描述,也可以编译成二进制的内容:bglTF。glTF可以包括场景、摄像机、动画等,也可以包括网格、材质、纹理,甚至包括了渲染技术(technique)、着色器以及着色器程序。

3.提出一个新的格式的好处是什么?

​ OpenGL已经成为事实上的工业渲染标准,作为现有OpenGL的维护组织,khronos (是由其成员所提供之基金支持的行业协会,专注于创立开放标准,免授权费的移动设备接口程序API) 也迫切希望在3D格式上进行一次统一,让glTF成为类似jpeg、mp3等常见的格式,glTF的提出给大家提供了一个新的思路以及解决方案。就好像json当初没有提出的时候,大家都会倾向使用xml来描述通用的可交换的数据格式,可是随着json的发展,xml不再是一枝独秀,逐渐形成了两强的局面,大家也有了更多的选择。我们可以利用现有的格式转换工具很轻松地将collada格式转换为glTF格式,而glTF对于OpenGL的兼容性。

3.1简单了解下“OpenGL”

  • OpenGL(全写Open Graphics Library)是指定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它用于三维图像(二维的亦可),是一个功能强大,调用方便的底层图形库。
  • OpenGL™ 是行业领域中最为广泛接纳的 2D/3D 图形 API,其自诞生至今已催生了各种计算机平台及设备上的数千优秀应用程序。OpenGL™ 是独立于视窗操作系统或其它操作系统的,亦是网络透明的。

4.“glTF”的结构图

结构图

  • json是一个表述,描述该模型的节点层级,材质,相机,动画等相关逻辑结构。
  • bin则对应这些对象的具体数据信息。
  • glsl是对该模型渲染的着色器,针对该模型的数据信息,给出渲染“配方”。
  • 当然还有纹理内容。大块内容可以以Base64的编码内迁到文件中,方便拷贝和加载,也可以以URl的外链方式,侧重重用性。

4.1 json中包含的描述信息

json中包含的描述信息,内容详细,比如mesh,纹理,蒙皮和动画,定义了accessor的访问器规则,同时还给出了相机,节点这些场景管理的信息。充分体现了glTF规范设计的强大,让我想到了一句话:“解决问题固然重要,但通过设计避免问题则更胜一筹”。可以说,该规范是对复杂三维模型的一个很不错的抽象,考虑的很充分,之间的接口定义也很规范.

结构图

4.2 "glTF"渲染

var entity = viewer.entities.add({
name : url,
position : position,
orientation : orientation,
model : {
uri : url,
minimumPixelSize : 128,
maximumScale : 20000
}
});

var model = scene.primitives.add(Cesium.Model.fromGltf({
url : './duck/duck.gltf'
}));

// 内部通过该方法来解析JSON对象,获取表述信息和具体的数据内容
function parseBinaryGltfHeader(uint8Array) {
var json = getStringFromTypedArray(uint8Array, sceneOffset, sceneLength);
return {
glTF: JSON.parse(json),
binaryOffset: binOffset
};
}

** 如上是加载glTF的过程,也是提供两种方式,一种是以Entity的方式,一种是以Primitive的方式,消费数码相机(前者)和单反相机(后者)的差别。同时,Cesium对Model的渲染也是基于状态的更新的,这个和地球,Entity的渲染思路是一致的。Model有三个状态,加载(NEEDS_LOAD),解析(LOADING),和结束(LOADED)。在不同状态下做该做的事,各司其职,互不干涉。**

Model.prototype.update = function(frameState) {
// Key 1 解析json对象中的各个对象
// 比如是否有动画,数据视图具体情况,是否有扩展属性等
if ((this._state === ModelState.NEEDS_LOAD) && defined(this.gltf)) {
parse(this);
this._state = ModelState.LOADING;
}

// Key 2 解析后,对需要调用的内容赋值
// 比如顶点数据和索引,材质,纹理等封装,动画,Runtime封装到对应的RuntimeNode
if (this._state === ModelState.LOADING) {
createResources(this, frameState);
this._state = ModelState.LOADED;
}

// Key 3 更新动态属性,传递到对应的着色器参数中
// 比如动画,骨骼等,更新对应变量的节点矩阵,重新梳理节点层级对应的矩阵等参数
if ((show && this._state === ModelState.LOADED) || justLoaded) {
updateNodeHierarchyModelMatrix(this, modelTransformChanged, justLoaded, frameState.mapProjection);
}

// 渲染队列
if (show && !this._ignoreCommands) {
var commandList = frameState.commandList;
// ……
}
}

** 如上是Model的状态更新函数,每一个状态只专注于自己的业务,当处理完后完成状态的更新。update实现实时更新和渲染。这里以读一本书为例来描述这个过程,首先,我们先解析glTF 的头信息,也就是json对象,了解该模型的大概结构,这就好比一本书的目录,当我们对一本书感兴趣的时候,都会先看看目录,了解一个大概;接着,我们开始解析glTF数据,将每一个结构中的数据解析赋值,这是最复杂,也是最关键的过程之一,我们开始逐章节的阅读这本书;最后,我们彻底解析完该数据,则构造对应的DrawCommand,添加到渲染队列中;如果该数据中包含一些时态数据,比如动画,蒙皮等,则每一帧都要动态的调整。这就是update中主要的四个状态和逻辑,完成该模型的渲染。**

4.3下面我们详细介绍这个过程中三个重要的部分。

4.3.1BufferView&Accessor

“glTF”格式学习

** 如图,红框部分,从下往上看。Buffer缓存是一个二进制的数据块,是几何对象,动画和蒙皮等数据信息的组合,在json中申明了这个数据块的类型arraybuffer和长度。BufferView,缓存视图,是Buffer的子集,如果Buffer是一本书的内容,那么BufferView就是一个目录,将这本书划分成章节,并表示该章节的起始页和长度。缓存和缓存视图并不包含类型信息。他们只是简单定义从文件中取出的原始数据,并不知道这些数据到底有什么涵义和结构。glTF文件中的对象(网格,蒙皮,动画)都不会直接访问缓存或缓存视图,而是通过Accessor访问器,这样我们拿到这块数据后,知道这块数据是vec4,float还是其他类型。  **

4.3.2Mesh

“glTF”格式学习

** 如上,有了访问的规范,我们还得知道一个几何对象的逻辑结构,就好比拼图游戏,我们能拿到一块块拼图,心中还要有一个轮廓,能把这些拼图拼成一个完整的图像。下面我们来看看Mesh这张图是有哪些部分构成的,一切的一切还是从上方图的红框开始。**

** 该Mesh可以有多个Primitive组成,每个图元有attribute顶点数据,indices顶点索引,mode类型为triangles,还有material材质,这些内容我们已经在之前的章节介绍过,不知道你还给我多少。我们再看material对象,里面用到了technique,其他的都是具体的光照模型的参数值,稍微特殊的是diffuse,是一张纹理。technique里面封装了着色器需要的参数,包括attribute和uniform,以及GL状态states,对应的着色器代码program,还有shaders,texture纹理的封装等,这些对象的值是一个accessor,进而获取对应的值。。这些对象我们之前都详细介绍过,我们顺藤摸瓜,算是对之前内容的温习,并串联成一个完整体系。可以说,里面的技术点都和以前的内容一样,glTF定义了他们之间交互的规范,将他们封装为一个整体。**

4.3.3Scene&Animation

“glTF”格式学习

** 在很多应用中,只是从一个建模数据包中带出单一对象,这并不充分。因此glTF还包含整个场景的关系,包括节点,变换矩阵,变换的层级关系,网格,材质,相机和动画,试图保存所有信息。这是一个场景树的逻辑,算是glTF的一个优化。如上图,该Scene中有三个node,其实Cesium_Air节点对应的mesh名字为Geometry-mesh090,他还有两个子节点。**

** 当然,Cesium内部提供了动画的解析(_runtime),在createRuntimeAnimations方法中实现,详细的自己来看。其中包括TIME计时器,samplers插值方式,所对应的动画节点和具体的属性(比如rotation)。这样每一帧会更新对应的值。**

5. 总结

Accessor&Json表述

通常,对一个二进制文件,我们都是按照规范格式逐个字节的解析,这样就有一个很大的风险,一步错了,后面的都会错。因为这太容易出错了,万一版本升级,多加了几个字节,就会导致整个文件无法解析,我们增加了超级纠错的机制。对每一块数据前面加一个长度,或者校验码,检测这块数据是否完整,每块读完后,根据这块的长度跳到下一个二进制块重新开始(而不是循序渐进),这样每一块坏了不会影响下一块。问题解决了,但并不实用,还仅仅是从技术层面的处理,所以我们需要增加规范,从设计上来解决这个问题,增加一个表述信息,根据accessor的规范来读取二进制流。(个人猜测protocol buffer也是这样的设计思路)

当然,如果实现这种读取方式,我们就需要一个“目录”页,glTF提供了json形式的header,这个header是json形式,也可以是xml格式,好处是灵活,兼容性强。相比xml,json是浏览器内部封装成对象,效率高,缺点查询不方便。


HappyGIS 版权所有, 本站文章可随意转载, 请注明出处: 转载请注明“glTF”格式学习
喜欢 (2)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址