创建和加载高级网格和几何体
在本章中,我们将探讨一些创建和加载高级和复杂几何体和网格的不同方法。 在第5章“学习使用几何体”和第6章“探索高级几何体”中, 我们向您展示了如何使用Three.js的内置对象创建一些高级几何体。 在本章中,我们将使用以下两种方法来创建高级几何体和网格:
- 几何体分组和合并
- 从外部资源加载几何体
我们从“分组和合并”方法开始。
使用此方法,我们使用标准的Three.js分组(THREE.Group)和BufferGeometryUtils.mergeBufferGeometries()函数来创建新对象。
几何体分组和合并
在本节中,我们将查看Three.js的两个基本功能: 将对象分组在一起和将多个几何体合并为单个几何体。 我们将从分组对象开始 。
将对象分组在一起
在先前的一些章节中,您已经看到在使用多个材质时如何将对象分组。 当使用多个材质从几何体创建网格时,Three.js会创建一个组。 您的几何体的多个副本被添加到此组中,每个副本都有自己的特定材质。 这个组被返回,因此它看起来像一个使用多个材质的网格。 但实际上,它是一个包含许多网格的组。
创建组非常简单。
您创建的每个网格都可以包含子元素,可以使用add函数添加子元素。
将子对象添加到组中的效果是您可以移动、缩放、旋转和转换父对象,
所有子对象也将受到影响。在使用组时,您仍然可以引用、修改和定位各个几何体。
您唯一需要记住的是所有位置、旋转和平移都是相对于父对象完成的。
让我们看一个示例(grouping.html)如下截图:
在这个例子中,您看到了大量的立方体,它们作为单个组添加到场景中。 在查看控件和使用组的效果之前,让我们快速看一下我们如何创建这个网格:
const size = 1;
const amount = 5000;
const range = 20;
const group = new THREE.Group();
const mat = new THREE.MeshNormalMaterial();
mat.blending = THREE.NormalBlending;
mat.opacity = 0.1;
mat.transparent = true;
for (let i = 0; i < amount; i++) {
const x = Math.random() * range - range / 2;
const y = Math.random() * range - range / 2;
const z = Math.random() * range - range / 2;
const g = new THREE.BoxGeometry(size, size, size);
const m = new THREE.Mesh(g, mat);
m.position.set(x, y, z);
group.add(m);
}
在这段代码片段中,您可以看到我们创建了一个THREE.Group实例。
该对象几乎与THREE.Object3D相同,
它是THREE.Mesh和THREE.Scene的基类,但本身不包含任何内容,
也不会导致任何内容被渲染。
在这个例子中,我们使用add函数向这个场景中添加了大量的立方体。
对于这个例子,我们添加了一个菜单,您可以使用该菜单更改网格的位置。
每当您使用此菜单更改属性时,THREE.Group对象的相关属性也会更改。
例如,在下一个例子中,您可以看到当我们缩放这个THREE.Group对象时,
所有嵌套的立方体也会被缩放:
如果您想更多地尝试使用THREE.Group对象,
一个很好的练习是修改示例,以便THREE.Group实例本身在x轴上旋转,
而各个立方体在它们的y轴上旋转。
使用THREE.Group的性能影响
在我们转到下一节,看看合并的地方,关于性能的一个快速说明。
当您使用THREE.Group时,此组内的所有单独网格都被视为单独的对象,
Three.js需要管理和渲染这些对象。如果场景中有大量的对象,您会看到性能明显下降。
如果您查看图8.2左上角,您会看到屏幕上有5000个立方体时,
我们的帧速率(FPS)大约为56。
并不算太糟糕,但通常我们的运行速度会在120 FPS左右。
Three.js提供了另一种方式,我们仍然可以控制单独的网格,但获得更好的性能。
这是通过THREE.InstancedMesh实现的。
如果要渲染具有相同几何体但具有不同变换(例如旋转、缩放、颜色或任何其他矩阵变换)的大量对象,
这个对象非常适用。
我们创建了一个名为instanced-mesh.html的示例,展示了它是如何工作的。
在这个例子中,我们渲染了250,000个立方体,仍然保持了良好的性能:
要使用THREE.InstancedMesh对象,我们创建它的方式与我们创建THREE.Group实例的方式相似:
const size = 1;
const amount = 250000;
const range = 20;
const mat = new THREE.MeshNormalMaterial();
mat.opacity = 0.1;
mat.transparent = true;
mat.blending = THREE.NormalBlending;
const g = new THREE.BoxGeometry(size, size, size);
const mesh = new THREE.Inst
ancedMesh(g, mat, amount);
for (let i = 0; i < amount; i++) {
const x = Math.random() * range - range / 2;
const y = Math.random() * range - range / 2;
const z = Math.random() * range - range / 2;
const matrix = new THREE.Matrix4();
matrix.makeTranslation(x, y, z);
mesh.setMatrixAt(i, matrix);
}
创建THREE.InstancedMesh对象与THREE.Group的主要区别在于,
我们需要预先定义要使用的材质和几何体,以及我们要创建多少个此几何体的实例。
要定位或旋转我们的实例之一,我们需要使用THREE.Matrix4实例提供变换。
幸运的是,我们不需要深入矩阵背后的数学,
因为Three.js在THREE.Matrix4实例上提供了一些辅助函数,
用于定义旋转、平移和其他一些变换。
在这个例子中,我们只是将每个实例放在一个随机位置。
因此,如果您正在处理少量网格(或使用不同几何体的网格),
如果要将它们组合在一起,应使用THREE.Group对象。
如果处理共享几何体和材质的大量网格,
可以使用THREE.InstancedMesh对象或
THREE.InstancedBufferGeometry对象以获得显著的性能提升。
在下一节中,我们将看一下合并,在那里您将合并多个单独的几何体,
最终得到一个单独的THREE.Geometry对象。
合并几何体
在大多数情况下,使用组可以轻松操作和管理大量的网格。
然而,当您处理非常大量的对象时,性能将成为一个问题,
因为Three.js必须单独处理组的所有子元素。
通过BufferGeometryUtils.mergeBufferGeometries,
您可以将几何体合并在一起,创建一个组合的几何体,
因此Three.js只需要管理这个单一的几何体。
在图8.4中,您可以看到这是如何工作的以及它对性能的影响。
如果您打开merging.html示例,您会再次看到一个场景,
其中有相同分布的半透明立方体,
但我们将它们合并成了一个THREE.BufferGeometry对象:
正如您所看到的,我们可以轻松渲染50,000个立方体而不会出现性能下降。
为此,我们使用以下几行代码:
const size = 1;
const amount = 500000;
const range = 20;
const mat = new THREE.MeshNormalMaterial();
mat.blending = THREE.NormalBlending;
mat.opacity = 0.1;
mat.transparent = true;
const geoms = [];
for (let i = 0; i < amount; i++) {
const x = Math.random() * range - range / 2;
const y = Math.random() * range - range / 2;
const z = Math.random() * range - range / 2;
const g = new THREE.BoxGeometry(size, size, size);
g.translate(x, y, z);
geoms.push(g);
}
const merged = BufferGeometryUtils.mergeBufferGeometries(geoms);
const mesh = new THREE.Mesh(merged, mat);
在这段代码片段中,我们创建了大量的THREE.BoxGeometry对象,
然后使用BufferGeometryUtils.mergeBufferGeometries(geoms)
函数将它们合并在一起。
结果是一个单一的大型几何体,我们可以将其添加到场景中。
最大的缺点是您失去了对个别立方体的控制,因为它们都被合并成一个大型几何体。
如果要移动、旋转或缩放单个立方体,
您将无法实现(除非搜索正确的面和顶点并分别定位它们)。
通过构造实体几何体创建新几何体
除了像本章中所见的合并几何体的方式外,
我们还可以使用构造实体几何体(CSG)来创建几何体。
使用CSG,您可以对两个几何体应用操作(通常是加法、减法、差异和交集),
以将它们组合在一起。这些库将基于所选操作创建一个新的几何体。
例如,使用CSG,可以非常容易地在一个侧面上创建具有球状凹陷的实心立方体。
您可以使用Three.js的three-bvh-csg(https://github.com/gkjohnson/three-bvh-csg)和
Three.csg(https://github.com/looeee/threejs-csg)等库来实现这一点。
通过分组和合并的方法,您可以使用Three.js提供的基本几何体创建大型且复杂的几何体。 如果要创建更高级的几何体,则使用Three.js提供的编程方法并非总是最佳和最简便的选项。 幸运的是,Three.js提供了其他几种选项来创建几何体。 在下一节中,我们将看看如何从外部资源加载几何体和网格。
从外部资源加载几何 体
Three.js可以读取许多3D文件格式,并导入在这些文件中定义的几何体和网格。
需要注意的是,并非始终支持这些格式的所有功能。
因此,有时可能会出现纹理问题,或者材质可能没有正确设置的情况。
用于交换模型和纹理的新事实上的标准是glTF,因此,如果要加载外部创建的模型,
将这些模型导出到glTF格式通常会在Three.js中获得最佳结果。
在本节中,我们将更深入地了解一些由Three.js支持的格式,但不会向您展示所有加载器。 以下列表概述了Three.js支持的格式:
AMF:AMF是另一种3D打印标准,但不再处于积极开发中。 有关此标准的更多信息,请参见维基百科页面。3DM:3DM是Rhinoceros使用的格式,Rhinoceros是创建3D模型的工具。 有关Rhinoceros的更多信息,请查看此处。3MF:3MF是3D打印中使用的标准之一。 有关此格式的信息,请访问3MF联盟主页。COLLAborative Design Activity (COLLADA):COLLADA是一种使用基于XML的格式定义数字资产的格式。 这是一种广泛使用的格式,几乎所有3D应用程序和渲染引擎都支持。Draco:Draco是一种以非常高效的方式存储几何体和点云的文件格式。 它规定了这些元素最佳的压缩和解压缩方式。 有关Draco的详细信息,请参见其GitHub页面:https://github.com/google/draco。GCode:GCode是与3D打印机或数控机床交流的标准方式。 在打印模型时,3D打印机可以通过发送GCode命令来进行控制。 此标准的详细信息在此论文中有描述。glTF:这是一种规范,定义了3D场景和模型如何在不同应用程序和工具之间交换和加载, 并且正在成为在Web上交换模型的标准格式。 它们以二进制格式(.glb扩展名)和基于文本的格式(.gltf扩展名)提供。 有关此标准的更多信息,请参见这里。Industry Foundation Classes (IFC):这是建筑信息建模(BIM)工具使用的开放文件格式。 它包含建筑物的模型以及有关使用的材料的大量附加信息。 有关此标准的更多信息,请参见此处。JSON:Three.js有自己的JSON格式,您可以使用它来声明性地定义几何体或场景。 尽管这不是官方格式,但在想要重用复杂几何体或场景时,它非常易于使用且非常方便。KMZ:这是Google Earth上用于3D资产的格式。 有关更多信息,请访问此处。LDraw:LDraw是一种开放标准,可用于创建虚拟LEGO模型和场景。 有关更多信息,请查看LDraw主页。LWO:这是LightWave 3D使用的文件格式。 有关LightWave 3D的更多信息,请查看此处。NRRD:NRRD是用于可视化体积数据的文件格式。 例如,它可以用于渲染CT扫描。可以在此处找到大量信息和示例。OBJ和MTL:OBJ是Wavefront Technologies首次开发的简单3D格式。 它是最广泛采用的3D文件格式之一,用于定义对象的几何形状。MTL是OBJ的伴随格式,在MTL文件中指定了OBJ文件中对象的材质。 Three.js还有一个自定义的OBJ导出器,称为OBJExporter, 如果您想要从Three.js导出模型到OBJ,可以使用它。PCD:这是描述点云的开放格式。 有关更多信息,请访问此处。PDB:这是由蛋白质数据银行(PDB)创建的非常专业化的格式,用于指定蛋白质的外观。 Three.js可以加载和可视化以此格式指定的蛋白质。Polygon File Format (PLY):这通常用于存储来自3D扫描仪的信息。Packed Raw WebGL Model (PRWM):这是另一种专注于高效存储和解析3D几何体的格式。 有关此标准以及如何使用它的更多信息,请参见此处。STereoLithography (STL):这在快速原型制作中被广泛使用。 例如,3D打印机的模型通常以STL文件定义。 Three.js还有一个自定义的STL导出器,称为STLExporter.js, 如果您想要从Three.js导出模型 到STL,可以使用它。SVG:SVG是定义矢量图形的标准方法。 此加载器允许您加载SVG文件并返回一组THREE.Path元素,您可以用于挤压或在2D中渲染。3DS:Autodesk 3DS格式。有关更多信息,请查看此处。TILT:TILT是Tilt Brush使用的格式,Tilt Brush是一种允许您在虚拟现实中绘画的工具。 有关更多信息,请访问此处。VOX:MagicaVoxel使用的格式,MagicaVoxel是一种可以用来创建体素艺术的免费工具。 有关更多信息,请查看MagicaVoxel的主页:https://ephtracy.github.io/。Virtual Reality Modeling Language (VRML):这是一种文本格式,允许您指定3D对象和世界。 它已被X3D文件格式取代。Three.js不支持加载X3D模型,但可以将这些模型轻松转换为其他格式。 有关更多信息,请访问X3D主页。Visualization Toolkit (VTK):这是由VTK定义并用于指定顶点和面的文件格式。 有两种可用的格式:一种是二进制格式,另一种是基于文本的ASCII格式。Three.js仅支持基于ASCII的格式。XYZ:这是用于描述三维空间中点的非常简单的文件格式。 有关更多信息,请访问此处。
在第9章《动画和摄像机移动》中,当我们查看动画时,我们将重新访问其中的一些格式(并查看一些附加的格式)。
正如您从这个 列表中所看到的,Three.js支持非常多的3D文件格式。 我们不会描述其中的所有格式,只描述一些最有趣的。 我们将从JSON加载器开始,因为它提供了一种很好的方式来存储和检索您自己创建的场景。
在Three.js中保存和加载JSON格式
在Three.js中,您可以使用JSON格式执行两种不同的场景。
您可以使用它来保存和加载单个THREE.Object3D对象(这意味着您也可以使用它来导出THREE.Scene对象)。
为了演示保存和加载,我们创建了一个基于THREE.TorusKnotGeometry的简单示例。
通过这个示例,您可以创建一个环结,
就像我们在第5章中所做的那样,并且可以使用保存/加载菜单中的保存按钮保存当前几何体。
对于此示例,我们使用HTML5本地存储API进行保存。
此API允许我们轻松地将持久信息存储在客户端浏览器中,
并在以后的某个时间检索它(甚至在浏览器已关闭并重新启动后):
在上面的截图中,您可以看到两个网格——红色的是我们加载的网格,黄色的是原始网格。 如果您自己打开此示例并单击保存按钮,则将存储网格的当前状态。 现在,您可以刷新浏览器,单击加载,保存的状态将以红色显示。
在Three.js中从JSON中导出非常简单,不需要您包含任何其他库。
唯一需要做的是将THREE.Mesh导出为JSON并将其存储在浏览器的本地存储中,如下所示:
const asJson = mesh.toJSON();
localStorage.setItem('json', JSON.stringify(asJson));
在保存之前,我们首先使用JSON.stringify函数将toJSON函数的结果(一个JavaScript对象)转换为字符串。
要使用HTML5本地存储API保存此信息,我们只需调用localStorage.setItem函数。
第一个参数是键值(json),我们稍后可以使用它来检索我们作为第二个参数传递的信息。
此JSON字符串如下所示:
{
"metadata": {
"version": 4.5,
"type": "Object",
"generator": "Object3D.toJSON"
},
"geometries": [
{
"uuid": "15a98944-91a8-45e0-b974-0d505fcd12a8",
"type": "TorusKnotGeometry",
"radius": 1,
"tube": 0.1,
"tubularSegments": 200,
"radialSegments": 10,
"p": 6,
"q": 7
}
],
"materials": [
{
"uuid": "38e11bca-36f1-4b91-b3a5-0b2104c58029",
"type": "MeshStandardMaterial",
"color": 16770655,
// 省略了一些材质属性
"stencilFuncMask": 255,
"stencilFail": 7680,
"stencilZFail": 7680,
"stencilZPass": 7680
}
],
"object": {
"uuid": "373db2c3-496d-461d-9e7e-48f4d58a507d",
"type": "Mesh",
"castShadow": true,
"layers": 1,
"matrix": [
0.5,
...
1
],
"geometry": "15a98944-91a8-45e0-b974-0d505fcd12a8",
"material": "38e11bca-36f1-4b91-b3a5-0b2104c58029"
}
}
正如您所见,Three.js保存了关于THREE.Mesh对象的所有信息。
将THREE.Mesh加载回Three.js也只需要几行代码,
如下所示:
const fromStorage = localStorage.getItem('json');
if (fromStorage) {
const structure = JSON.parse(fromStorage);
const loader = new THREE.ObjectLoader();
const mesh = loader.parse(structure);
mesh.material.color = new THREE.Color(0xff0000);
scene.add(mesh);
}
在这里,我们首先使用我们保存的名称(在这种情况下为json)从本地存储中获取JSON。
为此,我们使用HTML5本地存储API提供的localStorage.getItem函数。
接下来,我们需要将字符串转换回JavaScript对象(JSON.parse),
并将JSON对象转换回THREE.Mesh。
Three.js提供了一个称为THREE.ObjectLoader的辅助对象,
您可以使用它将JSON转换为THREE.Mesh。
在这个例子中,我们在加载器上使用了parse方法直接解析JSON字符串。
加载器还提供了一个load函数,您可以将URL传递给包含JSON定义的文件。
正如您在这里看到的,我们只保存了一个THREE.Mesh对象,因此失去了其他一切。
如果要保存包括灯光和摄像机在内的完整场景,您可以使用相同的方法导出场景:
const asJson = scene.toJSON();
localStorage.setItem('scene', JSON.stringify(asJson));
这的结果是JSON中的完整场景描述:
这可以以与我们已经展示的THREE.Mesh对象相同的方式加载。
尽管在专门使用Three.js时,将当前场景和对象存储在JSON中非常方便,
但这不是一种可以轻松与其他工具和程序交换或创建的格式。
在接下来的部分中,我们将更深入地了解Three.js支持的一些3D格式。
从3D文件格式导入
在本章的开头,我们列举了一些Three.js支持的格式。 在这一节中,我们将快速浏览一下其中一些格式的示例。
OBJ和MTL格式
OBJ和MTL是配套格式,通常一起使用。
OBJ文件定义了几何形状,而MTL文件定义了使用的材质。
OBJ和MTL都是基于文本的格式。
OBJ文件的一部分如下所示:
v -0.032442 0.010796 0.025935
v -0.028519 0.013697 0.026201
v -0.029086 0.014533 0.021409
usemtl Material
s 1
f 2731 2735 2736 2732
f 2732 2736 3043 3044
MTL文件定义了材质,如下所示:
newmtl Material
Ns 56.862745
Ka 0.000000 0.000000 0.000000
Kd 0.360725 0.227524 0.127497
Ks 0.010000 0.010000 0.010000
Ni 1.000000
d 1.000000
illum 2
OBJ和MTL格式在Three.js中得到很好的支持,因此如果您想要交换3D模型,这是一个不错的选择。
Three.js有两个不同的加载器可供使用。
如果只想加载几何体,可以使用OBJLoader。我们在我们的示例(load-obj.html)中使用了此加载器。
以下截图显示了此示例:
从外部文件加载OBJ模型的方法如下:
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
new OBJLoader().loadAsync('/assets/models/baymax/Bigmax_White_OBJ.obj').then((model) => {
model.scale.set(0.05, 0.05, 0.05);
model.translateY(-1);
visitChildren(model, (child) => {
child.receiveShadow = true;
child.castShadow = true;
});
return model;
});
在此代码中,我们使用OBJLoader异步加载模型。
这返回一个JavaScript promise,当解析时,将包含网格。
加载模型后,我们进行一些微调,并确保模型既投射阴影又接收阴影。
除了loadAsync,每个加载器还提供了一个load函数,该函数与回调一起工作,而不是使用promise。
使用回调的相同代码可能如下所示:
const model = new OBJLoader().load('/assets/models/baymax/Bigmax_White_OBJ.obj', (model) => {
model.scale.set(0.05, 0.05, 0.05);
model.translateY(-1);
visitChildren(model, (child) => {
child.receiveShadow = true;
child.castShadow = true;
});
// 对模型进行其他处理
scene.add(model);
});
在本章中,我们将使用基于Promise的loadAsync方法,因为这样可以避免嵌套回调,
并使得链式调用这些方法变得更加容易。
下一个示例(load-obj-mtl.html)使用OBJLoader与MTLLoader一起加载模型并直接分配材质。
以下截图显示了此示例:
除了OBJ文件之外,还使用MTL文件的原理与本节前面看到的相同:
const model = mtlLoader.loadAsync('/assets/models/butterfly/butterfly.mtl').then((materials) => {
objLoader.setMaterials(materials);
return objLoader.loadAsync('/assets/models/butterfly/butterfly.obj').then((model) => {
model.scale.set(30, 30, 30);
visitChildren(model, (child) => {
// 如果已经存在法线,则无法合并顶点
child.geometry.deleteAttribute('normal');
child.geometry = BufferGeometryUtils.mergeVertices(child.geometry);
child.geometry.computeVertexNormals();
child.material.opacity = 0.1;
child.castShadow = true;
});
const wing1 = model.children[4];
const wing2 = model.children[5];
[0, 2, 4, 6].forEach(function (i) {
model.children[i].rotation.z = 0.3 * Math.PI;
});
[1, 3, 5, 7].forEach(function (i) {
model.children[i].rotation.z = -0.3 * Math.PI;
});
wing1.material.opacity = 0.9;
wing1.material.transparent = true;
wing1.material.alphaTest = 0.1;
wing1.material.side = THREE.DoubleSide;
wing2.material.opacity = 0.9;
wing2.material.depthTest = false;
wing2.material.transparent = true;
wing2.material.alphaTest = 0.1;
wing2.material.side = THREE.DoubleSide;
return model;
});
});
在查看代码之前,首先要注意的一点是,
如果您收到OBJ文件、MTL文件和所需的纹理文件,您需要检查MTL文件如何引用纹理。
这些应该相对于MTL文件进行引用,而不是绝对路径。
代码本身与我们为THREE.ObjLoader看到的代码并没有太大区别。
我们首先使用THREE.MTLLoader对象加载MTL文件,
加载的材质通过setMaterials函数设置给THREE.ObjLoader。
我们用作示例的模型在这种情况下比较复杂。 因此,在回调中设置了一些特定的属性,以解决一些渲染问题, 如下所示:
- 我们需要合并模型中的顶点,以便其呈现为平滑的模型。
为此,我们首先需要从加载的模型中删除已定义的法线向量,
以便使用
BufferGeometryUtils.mergeVertices和computeVertexNormals函数向Three.js提供正确渲染模型的信息。 - 源文件中的不透明度设置不正确,导致翅膀不可见。 因此,为了修复这个问题,我们自己设置了不透明度和透明属性。
- 默认情况下,Three.js只渲染对象的一侧。
由于我们从两侧看翅膀,我们需要将
side属性设置为THREE.DoubleSide。 - 翅膀在彼此之上渲染时引起了一些不希望的伪影。
我们通过设置
alphaTest属性来解决这个问题。
但是,正如您所看到的,您可以轻松地直接将复杂模型加载到Three.js中,并在浏览器中实时渲染它们。 但您可能需要微调各种材质属性。
加载gLTF模型
我们已经提到,在Three.js中导入数据时,glTF是一个很好的格式。
为了向您展示即使是导入和显示复杂场景也有多么简单,我们添加了一个示例,
我们只是从https://sketchfab.com/3d-models/sea-house-bc4782005e9646fb9e6e18df61bfd28d中获取了一个模型:
正如您从前面的截图中所看到的,
这不是一个简单的场景,而是一个复杂的场景,
有许多模型、纹理、阴影和其他元素。
要在Three.js中实现这一点,我们所需做的只是这样:
const loader = new GLTFLoader();
return loader.loadAsync('/assets/models/sea_house/scene.gltf').then((structure) => {
structure.scene.scale.setScalar(0.2, 0.2, 0.2);
visitChildren(structure.scene, (child) => {
if (child.material) {
child.material.depthWrite = true;
}
});
scene.add(structure.scene);
});
您已经熟悉异步加载器,我们需要修复的唯一问题是确保正确设置了材质的depthWrite属性(这似乎是一些glTF模型常见的问题)。
就是这样 - 它只是运行。glTF还允许我们定义动画,这是我们将在下一章更仔细研究的内容。
展示完整的乐高模型
除了3D模型,其中模型定义了顶点、材质、灯光等,还有一些不明确定义几何形状但具有更具体用途的各种文件格式。
我们将在本节中介绍的LDrawLoader加载器是为了呈现3D乐高模型而创建的。
使用这个加载器的方法与我们已经看到几次的方法相同:
loader.loadAsync('/assets/models/lego/10174-1-ImperialAT-ST-UCS.mpd_Packed.mpd').then((model) => {
model.scale.set(0.015, 0.015, 0.015);
model.rotateZ(Math.PI);
model.rotateY(Math.PI);
model.translateY(1);
visitChildren(model, (child) => {
child.castShadow = true;
child.receiveShadow = true;
});
scene.add(model);
});
结果看起来真的很棒:
正如您所看到的,它展示了一个完整的乐高套装的结构。有许多不同的模型可供使用:
如果您想探索更多模型,可以从LDraw存储库下载它们:https://omr.ldraw.org/。
加载基于体素的模型
创建3D模型的另一种有趣方法是使用体素。这使您可以使用小立方体构建模型,并使用Three.js渲染它们。例如,您可以使用这样的工具在Minecraft之外创建Minecraft结构,并在以后导入它们到Minecraft中。一个用于尝试体素的免费工具是MagicaVoxel(https://ephtracy.github.io/)。该工具允许您创建像这样的体素模型:
有趣的部分是您可以使用VOXLoader加载器轻松在Three.js中导入这些模型,如下所示:
new VOXLoader().loadAsync('/assets/models/vox/monu9.vox').then((chunks) => {
const group = new THREE.Group();
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const mesh = new VOXMesh(chunk);
mesh.castShadow = true;
mesh.receiveShadow = true;
group.add(mesh);
}
group.scale.setScalar(0.1);
scene.add(group);
});
在models文件夹中,您可以找到一些vox模型。以下截图显示了在Three.js中加载的模型的样子:
下一个加载器是另一个非常特定的加载器。我们将看看如何从PDB格式渲染蛋白质。