什么是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 领域常用)创建时,画面是这样的
(※ 由于类型也很重要,你需要相应调整)verticestriangles
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。
扩展(扩展和额外内容)
- 注释
- https://github.com/KhronosGroup/glTF/blob/main/extensions/README.md
- 一些扩展保留为 的前缀中的规范
KHR_ - 示例:向节点添加灯光
"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的时代!