既然你已经理解了 glTF、glb,试着用 Python 来创建它
什么是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的时代!

浙公网安备 33010602011771号