更多精彩内容尽在数字孪生平台。
虽然在Blender等软件中创建 3D 模型更为常见,但有时我们希望在代码中创建模型。例如,从一种 3D 文件格式转换为另一种 3D 文件格式时,或者使用程序生成创建几何体时。
本文主要解释如何使用 glTF Transform ( gltf-transform.dev ) JavaScript 库以编程方式创建 glTF 3D 模型。该库压缩后的重量约为 20kb,比 Three.js 或 Babylon.js 等完整 3D 引擎小得多。
首先,我们需要一些顶点数据。glTF 支持点、线和三角网。Three.js 具有许多常见 3D 形状的开源实现,我复制了THREE.TorusGeometry
下面的相关部分,删除了对 Three.js 本身的依赖。
// MIT License. Copyright © 2010-2023 three.js authors.
function createTorus(radius = 1, tube = 0.4, radialSegments = 12,tubularSegments = 48, arc = Math.PI * 2) {
const indicesArray = [];
const positionArray = [];
const uvArray = [];
const vertex = [0, 0, 0];
// 生成 positions 和 uvs
for (let j = 0; j <= radialSegments; j++) {
for (let i = 0; i <= tubularSegments; i++) {
const u = (i / tubularSegments) * arc;
const v = (j / radialSegments) * Math.PI * 2;
// position
vertex[0] = (radius + tube * Math.cos(v)) * Math.cos(u);
vertex[1] = (radius + tube * Math.cos(v)) * Math.sin(u);
vertex[2] = tube * Math.sin(v);
positionArray.push(vertex[0], vertex[1], vertex[2]);
// uv
uvArray.push(i / tubularSegments);
uvArray.push(j / radialSegments);
}
}
// 生成索引
for (let j = 1; j <= radialSegments; j++) {
for (let i = 1; i <= tubularSegments; i++) {
// indices
const a = (tubularSegments + 1) * j + i - 1;
const b = (tubularSegments + 1) * (j - 1) + i - 1;
const c = (tubularSegments + 1) * (j - 1) + i;
const d = (tubularSegments + 1) * j + i;
// faces
indicesArray.push(a, b, d);
indicesArray.push(b, c, d);
}
}
return { indicesArray, positionArray, uvArray };
}
上面的函数createTorus()
提供了顶点位置和 UV 的数组,以及连接这些顶点的三角形的索引。这里可以替换为其他的几何图形。
接下来我们需要将顶点数据组装成 glTF 2.0 文件。首先,我们创建一个新的 document 对象和一个用于存储数据的Buffer。
import { Document } from '@gltf-transform/core';
const document = new Document();
const buffer = document.createBuffer();
接下来,我们获取上面生成的顶点数据,并使用它来创建Mesh。
const { indicesArray, positionArray, uvArray } = createTorus();
// indices and vertex attributes
const indices = document
.createAccessor()
.setArray(new Uint16Array(indicesArray))
.setType('SCALAR')
.setBuffer(buffer);
const position = document
.createAccessor()
.setArray(new Float32Array(positionArray))
.setType('VEC3')
.setBuffer(buffer);
const texcoord = document
.createAccessor()
.setArray(new Float32Array(texcoordArray))
.setType('VEC2')
.setBuffer(buffer);
// material
const material = document.createMaterial()
.setBaseColorHex(0xD96459)
.setRoughnessFactor(1)
.setMetallicFactor(0);
// primitive and mesh
const prim = document
.createPrimitive()
.setMaterial(material)
.setIndices(indices)
.setAttribute('POSITION', position)
.setAttribute('TEXCOORD_0', texcoord);
const mesh = document.createMesh('MyMesh')
.addPrimitive(prim);
虽然 glTF可用于单独存储Mesh,但更常见的是将Mesh放置在默认场景中。这可确保一切内容在各种 3D 查看器中按预期显示。我们将Mesh添加到节点,并将该节点放入场景中。如果我们有多个Mesh,我们会为每个节点分配不同的位置/旋转/缩放。
const node = document.createNode('MyNode')
.setMesh(mesh)
.setTranslation([0, 0, 0]);
const scene = document.createScene('MyScene')
.addChild(node);
最后,我们将结果保存为新的 glTF 文件。I/O 取决于我们运行代码的环境,因此请选择下面适当的选项。
Node.js
import { NodeIO } from '@gltf-transform/core';
const io = new NodeIO();
await io.write('./torus.glb', document);
Deno
import { DenoIO } from '@gltf-transform/core';
const io = new DenoIO();
await io.write('./torus.glb', document);
Web
import { WebIO } from '@gltf-transform/core';
const io = new WebIO();
const bytes = await io.writeBinary(document); // → Uint8Array
就这样,我们就生成了一个新 glTF 2.0 文件。
gltf-transform库可以做的不仅仅是创建新文件,它更常用于优化现有的 glTF 文件,我们可以通过在模型中添加 Draco 压缩来执行相同的操作。