本文部分内容为Three.js Journey课程的学习笔记
三维模型的格式很多种多样的:https://en.wikipedia.org/wiki/List_of_file_formats#3D_graphics。
有些格式专用于一种软件。有些已知非常轻,但有时缺乏具体数据。众所周知,有些几乎包含您可能需要的所有数据,但它们很重。有些格式是开源的,有些格式不是,有些是二进制的,有些是 ASCII 的,等等。
如果需要精确的数据并且无法找到软件支持的适当格式,您甚至可以轻松创建自己的格式。
下面是比较常见的三维数据格式:
GLTF支持非常多不同的数据集。可以同时拥有几何形状和材质等数据,但可以拥有相机、灯光、场景图、动画、骨架、变形甚至多个场景等数据。
它还支持各种文件格式,如 json、二进制、嵌入纹理。
GLTF 已成为实时性的标准。由于它正在成为一种标准,因此大多数 3D 软件、游戏引擎和库都支持它。这意味着可以在不同的环境中轻松获得相似的结果。
这并不意味着您在所有情况下都必须使用 GLTF。如果您只需要几何图形,则最好使用其他格式,例如 OBJ、FBX、STL 或 PLY。最好是在每个项目上测试不同的格式,看看是否拥有所需的所有数据、文件是否太重、如果信息被压缩则解压缩信息需要多长时间等。
GLTF团队还提供各种模型,从简单的三角形到逼真的模型,以及动画,变形,清漆材料等。https://github.com/KhronosGroup/glTF-Sample-Models可以拿来做测试用例
虽然 GLTF 本身是一种格式,但它也可以有不同的文件格式。这有点复杂,但有充分的理由。
常见的有:
其实还有其他更多的格式,这四种是比较常见的,下面分别了解一下这四种格式
此格式是一种默认格式,通常由种格式的文件组成:
当我们加载这种格式时,我们只需要加载包含对其他文件的引用的 .gltf ,然后会自动加载。
此格式仅由一个文件.glb组成。它包含我们以glTF默认格式包含的所有数据。这是一个二进制文件,不能只在代码编辑器中打开它来查看里面的内容。
这种格式可以更轻一些,加载起来更舒适,因为只有一个文件,但您将无法轻松更改其数据。例如,如果您想调整纹理的大小或压缩纹理,则不能,因为它位于该二进制文件中,因此无法与其余文件合并。
此格式类似于 glTF 默认格式,但缓冲区数据(通常是几何图形)使用 Draco 算法进行压缩。 通常.bin 文件会轻一些。
虽然此格式有一个单独的文件夹,但您可以将 Draco 压缩应用于其他格式。
这种格式类似于glTF-Binary格式,因为它只是一个文件,但这个文件实际上是一个可以在编辑器中打开的JSON。这种格式的唯一好处是只有一个易于编辑的文件。
如果希望能够在导出后更改灯光的纹理或坐标,则最好使用 glTF 默认值。它还具有单独加载不同文件的优点,从而提高了加载速度。
如果你每个模型只需要一个文件,并且不关心修改部分东西,最好还是选择glTF-Binary。
在这两种情况下,都必须决定是否要使用 Draco 压缩
要在 Three.js 中加载 GLTF 文件,得使用 GLTFLoader。默认情况下,此类在 THREE 变量中不可用。我们需要从位于 examples/ 依赖项中的 three 个文件夹中导入它:
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
/**
* Models
*/
const gltfLoader = new GLTFLoader()
如果需要,我们也可以使用 LoadManager
加载模型几乎和加载纹理一样简单,只需要调用 load(...)
方法并使用正确的参数:
gltfLoader.load(
'/models/Duck/glTF/Duck.gltf',
(gltf) =>{
console.log('success')
console.log(gltf)
},
(progress) =>{
console.log('progress')
console.log(progress)
},
(error) =>
{
console.log('error')
console.log(error)
}
)
查看控制台中记录的对象,您会发现很多元素。最重要的部分是 scene 属性,因为我们在导出的模型中只有一个场景。
将加载的模型添加到我们的场景:
三维模型的 scene 包含我们需要的一切。但它也包括更多。始终从研究其中可用的内容开始,并观察不同Groups, Object3D, 和Mesh的 scale 属性。
Mesh应该是我们的具体模型。我们不必太关心PerspectiveCamera。相机和具体模型似乎都在场景子数组children中的第一个也是唯一一个 Object3D 中。更糟糕的是,Object3D 将 scale 设置为最小值。
所以,即使加载成功获取到了相应的模型,这个模型也有点复杂,以为信息比较多
但是,我们想要的只是让我们的模型出现在场景中。我们有多种方法可以做到这一点:
如果我们的模型结构很简单,那就可以将 Object3D 添加到场景中,并忽略里面未使用的 PerspectiveCamera
gltfLoader.load(
'/models/Duck/glTF/Duck.gltf',
(gltf) =>
{
console.log(gltf.scene.children[0])
scene.add(gltf.scene.children[0])
}
)
多children模型加载
对于多children模型的加载就不能像上一样加载了
而是需要所以for循环遍历所有的模型添加到scene中:
gltfLoader.load(
'/models/FlightHelmet/glTF/FlightHelmet.gltf',
(gltf) =>
{
for(const child of gltf.scene.children)
{
scene.add(child)
}
// scene.add(gltf.scene.children[0])
}
)
但是这样加载并不能加载全,而且刷新网页后会得到不同的部分:
造成这样的问题是因为当我们将一个 child 从一个scene添加到另一个scene时,它会自动从第一个场景中删除。这意味着第一个场景中的孩子现在更少了。
当我们添加第一个对象时,它会从第一个场景中移除,第二个元素只是移动到第一个位置。但是您的循环现在采用数组的第二个元素。0 数组中将始终保留元素。
此问题有多种解决方案。第一个解决方案是获取加载场景的第一个子项并将其添加到我们的场景中,直到没有剩余项:
while(gltf.scene.children.length){
scene.add(gltf.scene.children[0])
}
这样才完整
另一种解决方案是复制 children 数组,以便拥有一个未更改的独立数组。为此,我们可以使用点差运算符 … 并将结果放入一个全新的数组 [] 中:
const children = [...gltf.scene.children]
for(const child of children)
{
scene.add(child)
}
最后一种最简单的解决方案是直接添加 scene 属性:
scene.add(gltf.scene)
加载所有Draco压缩过的模型并不能使用gltfLoader直接加载,而是需要为我们的 GLTFLoader 提供一个 DRACOLoader 实例,以便它可以加载压缩文件。
Draco 版本可能比默认版本轻得多。压缩应用于缓冲区数据(通常是几何图形)。无论您使用的是默认的 glTF、二进制 glTF 还是嵌入式 glTF,都没有关系
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
const dracoLoader = new DRACOLoader()
解码器在原生JavaScript可用,也可以在Web Assembly(wasm)中使用,并且可以在worker中运行(我们在物理课结束时看到的另一个线程)。这两个功能显著提高了性能,但它们意味着具有完全分离的代码。
这个 Draco 解码器文件夹位于 /node_modules/three/examples/js/libs/ 。可将整个 /draco/ 文件夹复制到你的静态文件目录中
dracoLoader.setDecoderPath('/draco/')
/**
* Models
*/
const gltfLoader = new GLTFLoader()
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load(
'/models/Duck/glTF-Draco/Duck.gltf',
(gltf) =>
{
// console.log(gltf.scene)
scene.add(gltf.scene)
}
)
和加载普通的模型没什么两样
/**
* Models
*/
const gltfLoader = new GLTFLoader()
gltfLoader.load(
'/models/Fox/glTF/Fox.gltf',
(gltf) =>
{
// console.log(gltf.scene)
gltf.scene.scale.set(0.025, 0.025, 0.025)
scene.add(gltf.scene)
}
)
如果查看加载的 gltf 对象,可以看到一个名为 animations 的属性包含多个 AnimationClip.
这些AnimationClip不能轻易使用。我们首先需要创建一个 AnimationMixer. 。AnimationMixer 类似于与可以包含一个或多个 AnimationClips 的对象关联的播放器。这个想法是为每个需要动画的对象创建一个。
在加载模型的回调函数中,创建一个混音器并将 gltf.scene 作为参数发送:
const mixer = new THREE.AnimationMixer(gltf.scene)
现在,我们可以使用 clipAction(…) 方法将动画剪辑添加到混音器中。让我们从第一个动画开始:
const action = mixer.clipAction(gltf.animations[0])
此方法返回一个 AnimationAction,我们最终可以在其上调用 play() 方法:
action.play()
要播放动画,我们必须告诉混音器在每一帧更新自身。问题是我们的 mixer 变量已在加载回调函数中声明,而我们在 tick 函数中无法访问它。为了解决这个问题,我们可以在 load 回调函数之外声明具有 null 值的 mixer 变量,并在加载模型时更新它:
let mixer = null
gltfLoader.load(
'/models/Fox/glTF/Fox.gltf',
(gltf) =>
{
gltf.scene.scale.set(0.03, 0.03, 0.03)
scene.add(gltf.scene)
mixer = new THREE.AnimationMixer(gltf.scene)
const action = mixer.clipAction(gltf.animations[0])
action.play()
}
)
最后,我们可以用已经计算出的 deltaTime 更新 tick 函数中的混音器。
const tick = () =>
{
// ...
if(mixer)
{
mixer.update(deltaTime)
}
// ...
}
动画应正在运行。可以通过更改 clipAction(…) 方法中的值来测试其他动画。
const action = mixer.clipAction(gltf.animations[2])
three.js有自己的在线编辑器。你可以在这里找到它: https://threejs.org/editor/
它就像一个3D软件,但在线且功能较少。您可以创建基元、光源、材质等。
由于您可以导入模型,因此这是测试模型是否正常工作的好方法。但是由于只能测试由一个文件组成的模型。你可以尝试使用 glTF-Binary 或 glTF-Embedded