什么是glTF?

  • 3D扫描数据通常存储在OBJ、PLY、STL、FBX等格式中。
  • 但这些不包括场景的结构和渲染方法
  • 由于点云数据采用所谓的“表格格式”,它可以为每个点提供属性,但很少有格式考虑为表面设置属性
  • Maya这样的3D建模工具可以在.max.blend中存储场景结构、光照设置、摄像机、动画和3D对象,如.马、3ds Max和Blend中的.blend。
  • 然而,这些需要专门的导入器加载器转换器
  • glTF定义了3D内容表示的标准规范,包括以下内容

特色

  • 用 JSON 编写,简洁且易于分析
  • 3D对象本身以普通工具可读取的格式保存

基本结构

  • 3D场景的全部内容都存储在JSON
  • 它被描述为场景结构,并具有定义场景图的节点层级结构。
  • 场景中显示的3D对象使用连接在节点上的网格
  • 材质、动画、骨骼和摄像机也被定义。

各元素概述

  • scene​是描述存储在glTF中场景的入门点。 指向定义场景图的节点
  • node​是场景图层级中的一个节点。 它可以包含变换(例如旋转或平移),还可以指代(子)节点。 此外,你还可以提及“附加”在节点上的网格或摄像实例,或者描述网格变体的皮肤
  • ​camera​​定义渲染场景的视图配置
  • mesh​描述场景中出现的几何对象。 指用于访问实际几何数据的访问对象,以及定义渲染时物体外观的材质
  • ​skin​​定义顶点蒙皮所需的参数,允许你根据虚拟角色的姿态来变换网格。 这些参数的值由访问器获得
  • ​animation​​描述特定节点的变形(如旋转或平移)随时间的变化
  • accessor​作为任何数据的抽象源。它被用于网格、皮肤和动画中,提供几何数据、蒙皮参数和时间相关的动画值。这里指的是bufferView,它是包含实际原始二进制数据的缓冲区的一部分
  • ​material​​包含定义物体外观的参数。通常,它指的是应用于渲染几何体的纹理对象
  • ​texture​​由采样器和图像定义。采样器定义了纹理图像如何放置在物体上
  • ​buffer​​和 是二元化并外部定义的(.bin・.jpg/.png)​​image​
  • ​buffer​​表示几何形状
  • ​image​​表示纹理

最小的glTF资产

基于上述,以下是最小的glTF:
它有层级结构,从场景中延伸而来,所以即使是最基本的结构也有些复杂。

{ "scene": 0, "scenes" : [ { "nodes" : [ 0 ] } ], "nodes" : [ { "mesh" : 0 } ], "meshes" : [ { "primitives" : [ { "attributes" : { "POSITION" : 1 }, "indices" : 0 } ] } ], "buffers" : [ { "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", "byteLength" : 44 } ], "bufferViews" : [ { "buffer" : 0, "byteOffset" : 0, "byteLength" : 6, "target" : 34963 }, { "buffer" : 0, "byteOffset" : 8, "byteLength" : 36, "target" : 34962 } ], "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 ] } ], "asset" : { "version" : "2.0" } }

各关的说明

  • 场景与节点
  • scene: 0表示它首先读取场景数组的第0个索引
  • scenes 有一个包含节点对象索引的数组,并进一步表明它获得了第 0 个节点索引

"scene": 0, "scenes" : [ { "nodes" : [ 0 ] } ], "nodes" : [ { "mesh" : 0 } ],

  • 网格
  • 展示三维几何对象
  • 网格通常会显得臃肿,所以它们通常只显示 mesh.primitive 对象数组,但在示例中,我们直接在网格内写入 mesh.primitives
  • mesh.primitive 有属性和索引,属性有一个 POSITION 属性表示顶点
  • 索引描述一个指向顶点的索引

"meshes" : [ { "primitives" : [ { "attributes" : { "POSITION" : 1 }, "indices" : 0 } ] } ],

  • 缓冲区
  • 缓冲区指的是原始的、非结构化的数据块。
  • URI 属性表示外部文件或 JSON 文件中的二进制数据
  • 换句话说,URI 属性是否包含顶点和三角形的信息?
  • 表示有一个缓冲区包含44字节

"buffers" : [ { "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", "byteLength" : 44 } ],

  • 缓冲视图
  • bufferViews 描述缓冲区的视图
  • 由于两者的缓冲区都是0,它们指定相同的缓冲区,但指定的字节不同。

"bufferViews" : [ { "buffer" : 0, "byteOffset" : 0, "byteLength" : 6, "target" : 34963 }, { "buffer" : 0, "byteOffset" : 8, "byteLength" : 36, "target" : 34962 } ],

  • 访问器
  • 指定数据类型和布局,并定义缓冲视图应如何解释。

"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 ] } ],

  • 此外,原语还可以通过访问器的索引来引用

"meshes" : [ { "primitives" : [ { "attributes" : { "POSITION" : 1 }, "indices" : 0 } ] } ],

  • 事实上,它的定义更为复杂,但目前,这就是

结论

因此,如果你想创建自己的 glTF......
- 节点从 scene
引用 - node 指网格
- mesh.primitives 指缓冲区
- 缓冲区定义为三维对象
的二进制 - bufferView 定义了要读取
的范围——如果访问器告诉你如何读取
,它将是一个文件

试试用Python来做

  • 如果你用 Python 的 pygltflib 做几乎一模一样的东西,它看起来是这样。

from pygltflib import * gltf = GLTF2() scene = Scene() mesh = Mesh() primitive = Primitive() node = Node() buffer = Buffer() bufferView1 = BufferView() bufferView2 = BufferView() accessor1 = Accessor() accessor2 = Accessor() buffer.uri = "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=" buffer.byteLength = 44 bufferView1.buffer = 0 bufferView1.byteOffset = 0 bufferView1.byteLength = 6 bufferView1.target = ELEMENT_ARRAY_BUFFER bufferView2.buffer = 0 bufferView2.byteOffset = 8 bufferView2.byteLength = 36 bufferView2.target = ARRAY_BUFFER accessor1.bufferView = 0 accessor1.byteOffset = 0 accessor1.componentType = UNSIGNED_SHORT accessor1.count = 3 accessor1.type = SCALAR accessor1.max = [2] accessor1.min = [0] accessor2.bufferView = 1 accessor2.byteOffset = 0 accessor2.componentType = FLOAT accessor2.count = 3 accessor2.type = VEC3 accessor2.max = [1.0, 1.0, 0.0] accessor2.min = [0.0, 0.0, 0.0] primitive.attributes.POSITION = 1 node.mesh = 0 scene.nodes = [0] gltf.scenes.append(scene) gltf.meshes.append(mesh) gltf.meshes[0].primitives.append(primitive) gltf.nodes.append(node) gltf.buffers.append(buffer) gltf.bufferViews.append(bufferView1) gltf.bufferViews.append(bufferView2) gltf.accessors.append(accessor1) gltf.accessors.append(accessor2) gltf.save("triangle.gltf")

从 np.ndarray(3D 领域常用)创建时,画面是这样的
(※ 由于类型也很重要,你需要相应调整)​​vertices​​​​triangles​

import numpy as np import pygltflib points = np.array( [ [-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [-0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0.5, -0.5, -0.5], [-0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [-0.5, 0.5, -0.5], ], dtype="float32", ) triangles = np.array( [ [0, 1, 2], [3, 2, 1], [1, 0, 4], [5, 4, 0], [3, 1, 6], [4, 6, 1], [2, 3, 7], [6, 7, 3], [0, 2, 5], [7, 5, 2], [5, 7, 4], [6, 4, 7], ], dtype="uint8", ) triangles_binary_blob = triangles.flatten().tobytes() points_binary_blob = points.tobytes() gltf = pygltflib.GLTF2( scene=0, scenes=[pygltflib.Scene(nodes=[0])], nodes=[pygltflib.Node(mesh=0)], meshes=[ pygltflib.Mesh( primitives=[ pygltflib.Primitive( attributes=pygltflib.Attributes(POSITION=1), indices=0 ) ] ) ], accessors=[ pygltflib.Accessor( bufferView=0, componentType=pygltflib.UNSIGNED_BYTE, count=triangles.size, type=pygltflib.SCALAR, max=[int(triangles.max())], min=[int(triangles.min())], ), pygltflib.Accessor( bufferView=1, componentType=pygltflib.FLOAT, count=len(points), type=pygltflib.VEC3, max=points.max(axis=0).tolist(), min=points.min(axis=0).tolist(), ), ], bufferViews=[ pygltflib.BufferView( buffer=0, byteLength=len(triangles_binary_blob), target=pygltflib.ELEMENT_ARRAY_BUFFER, ), pygltflib.BufferView( buffer=0, byteOffset=len(triangles_binary_blob), byteLength=len(points_binary_blob), target=pygltflib.ARRAY_BUFFER, ), ], buffers=[ pygltflib.Buffer( byteLength=len(triangles_binary_blob) + len(points_binary_blob) ) ], ) gltf.set_binary_blob(triangles_binary_blob + points_binary_blob)

现在你应该能输出你想要的glTF。
如需更详细的说明,请参阅官方README。


扩展(扩展和额外内容)

"nodes" : [ { "extensions" : { "KHR_lights_punctual" : { "light" : 0 } } } ]

  • 还有一种自由形式的扩展称为额外扩展,可以添加到任何属性上,如网格、缓冲区、节点等。

"nodes": [ { "extras": { "id": "0a4a5478b66849b3890a3d5b1de98e18", "name": "\u307f\u3069\u308a\u306e\u7a93\u53e3" }, "mesh": 0 } ],

  • 在Blender里,你可以通过把它加到节点的额外部分来引用它。

结论

如开头所述,大多数3D数据格式不能被视为3D GIS数据,因为它们的功能与表面不同,仅保留表面。​​属性​

不过,glTF允许你通过扩展自由保留属性,所以你可能能做3D GIS分析。

从现在开始,这就是glTF的时代!