现在您已经完全了解了 glTF,请尝试在 Python 中创建 glTF/glb
什么是 glTF?
3D 扫描数据通常以 OBJ、PLY、STL、FBX 等格式存储。
但是,这些不包括场景的结构或渲染方法
由于点云数据采用所谓的“表格格式”,因此它可以具有每个点的属性,但很少有格式会考虑具有面的属性
3D 建模工具(如 Maya)可以在 3ds Max 中以 .max 保存场景的结构、灯光设置、相机、动画、3D 对象体,并在 Blender 中保存 .blend
但是,这些需要创建自己的导入器加载器转换器
glTF 定义了 3D 内容表示的标准规范,包括以下内容
glb官方 glbxz.com 下载glb/gltf格式模型
特征
以 JSON 编写,紧凑且易于解析
3D 对象本身以常用工具可以读取的格式保存
基本结构
3D 场景的全部内容都存储在 JSON 中
它被描述为场景结构,并具有定义场景图的节点层次结构。
场景中显示的 3D 对象使用附加到节点的网格
材质、动漫骨架和摄像机也被定义
各种元素概述
scene是存储在 glTF 中的场景描述的入口点。 指向定义场景图的节点
node是场景图层次结构中的单个节点。 它可以包括转换(例如,旋转或平移),甚至可以引用(子)节点。 此外,还可以引用“附加”到节点的网格或摄像机的实例,或描述网格变形的蒙皮
camera定义用于渲染场景的视图配置
mesh描述场景中显示的几何对象。 指用于访问实际几何图形数据的访问器对象,以及定义对象在呈现时的外观的材料
skin定义顶点蒙皮所需的参数,并允许根据虚拟角色的姿势对网格进行变形。 这些参数的值是从访问器
animation描述特定节点的变形(如旋转或平移)如何随时间变化
accessor用作任意数据的抽象源。 它用于网格、皮肤和动画,以提供几何数据、蒙皮参数和与时间相关的动画值。 它引用 bufferView,它是包含实际原始二进制数据的缓冲区的一部分
material包含定义对象外观的参数。 它通常是指应用于渲染几何体的纹理对象
texture由 Sampler 和 Image 定义。 采样器定义纹理图像在对象上的位置
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 表示先读取 scenes 数组的第 0 个索引
scenes 中有一个包含节点对象索引的数组,这进一步表明我们想要获取节点的第 0 个索引
"scene": 0,
"scenes" : [
{
"nodes" : [ 0 ]
}
],
"nodes" : [
{
"mesh" : 0
}
],
网孔
表示 3D 几何对象
由于网格往往是臃肿的,因此通常只显示 mesh.primitive 对象的数组,但在示例中,mesh.primitives 直接在网格中描述
mesh.primitive 具有 attributes 和 indices,它们具有指示顶点的 POSITION 属性
indices 描述指示顶点的索引
"meshes" : [
{
"primitives" : [ {
"attributes" : {
"POSITION" : 1
},
"indices" : 0
} ]
}
],
缓冲区
buffer 表示原始的非结构化数据块
uri 属性指向外部文件或 JSON 文件的二进制数据
换句话说,uri 属性是否包含顶点和三角形信息?
指示有一个包含 44 字节的缓冲区
"buffers" : [
{
"uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=",
"byteLength" : 44
}
],
缓冲区视图
bufferViews 描述要缓冲的视图
两者都有 buffer: 0,所以指定了相同的缓冲区,并且指定的字节不同
"bufferViews" : [
{
"buffer" : 0,
"byteOffset" : 0,
"byteLength" : 6,
"target" : 34963
},
{
"buffer" : 0,
"byteOffset" : 8,
"byteLength" : 36,
"target" : 34962
}
],
访问
指定数据类型和布局,并定义应如何解释 bufferView
"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...
- 节点从场景中
引用 - 节点引用网格 - mesh.primitives 引用缓冲区
- 缓冲区定义 3D 对象的
二进制版本 - 如果要在 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")
如果你从 3D 中经常使用的 np.ndarray 制作它,它将如下所示
(* 类型似乎很重要,所以需要匹配)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
}
}
}
]
除此之外,还有一个名为 extras 的自由格式扩展,可以将其添加到任何属性中,例如网格、缓冲区或节点
"nodes": [
{
"extras": {
"id": "0a4a5478b66849b3890a3d5b1de98e18",
"name": "\u307f\u3069\u308a\u306e\u7a93\u53e3"
},
"mesh": 0
}
],
在 Blender 中,你可以通过将其添加到 Node 的 extras 来引用它。
结论
如开头所述,大多数 3D 数据格式不能被视为 3D GIS 数据,因为它们不具备这样的功能,只具有保持面的功能。属性
但是,使用 glTF,您可以使用扩展函数自由保留属性,因此可以执行 3D GIS 分析。
从此,就是 glTF 的时代!

浙公网安备 33010602011771号