赞
踩
Three.js允许我们创建许多基本几何图形,但是当涉及到更复杂的形状时,我们最好使用专业的3D软件。这次课将使用已经制作完成的模型,后面将会学习在3D软件中创建自己的模型。
随着时间推移,已经有非常多的3D模型格式文件,这也是为什么到目前为止我们有数百种模型格式可供使用https://en.wikipedia.org/wiki/List_of_file_formats#3D_graphics
GLTF即图形语言传输格式( Graphics Language Transmission Format or GL Transmission Format)。它在过去几年内非常流行。它支持非常多不同的数据集,可以很直观的具有几何体和材质等数据,同时也可以具有摄像机、灯光、场景图形、动画、骨架、变形甚至多场景等数据。不仅如此,它还支持各种文件格式,像是json、二进制文件(binary)、嵌入纹理(embed textures)。
glTF正在成为一种标准,大多数3D软件、游戏引擎和库都支持它。这意味着我们可以在不同的环境中轻松获得类似的结果。
当然这也并不是说所有情况下都得使用glTF,如果你只需要一个几何体,最好使用另一种格式,如OBJ、FBX、STL或PLY。
我们应该在每个项目上测试不同的格式,看看文件是否包含需要的所有数据,文件是否太大了,是否被压缩过了,被压缩后解压需要多长时间等等。
首先,我们需要一个模型。如前所述,我们将在稍后学习如何在3D软件中创建自己的模型,但现在,让我们使用预先制作的模型。
GLTF团队提供各种模型,从简单的三角形到逼真的模型,以及动画、变形、透明涂层材料等。https://github.com/KhronosGroup/glTF-Sample-Models
虽然GLTF本身就是一种格式,但它也可以有不同的文件版本格式。
如果打开/static/models/Duck/
文件夹,您将看到4个不同的文件夹。虽然每个都包含duck,但都采用不同的GLTF格式:
此格式是一种默认格式。Duck.gltf文件是可以在编辑器中打开的JSON文件。它包含各种信息,如相机、灯光、场景、材质、对象变换,但既不包含几何体geometry,也不包含纹理material。Duck0.bin文件是一个二进制文件,不能像上面那样去读取。它通常包含几何图形等数据以及与顶点相关的所有信息,如UV坐标、法线、顶点颜色等。DuckCM.png是鸭子的纹理。
当我们加载这种格式时,我们只加载Duck.gltf,其中包含对随后将自动加载的其他文件的引用。
此格式仅由一个glb文件组成。它包含我们在上面讨论的glTF默认文件格式中的所有数据。这是一个二进制文件,你不能在代码编辑器中打开它来查看其中的内容。
由于只有一个文件,这种格式可能会更轻一些,加载起来也更舒适,但你无法轻松更改其数据。例如,如果要调整纹理大小或压缩纹理,则无法将其与其他纹理合并,因为它位于该二进制文件中。
此格式类似于glTF默认格式,但缓冲区数据(通常是几何体)使用Draco算法进行压缩。如果你比较一下.bin
文件大小,你将看到它要小得多。下面会详细讲。
此格式类似于glTF-Binary格式,因为它只有一个文件,此文件实际上是一个JSON,因此可以在编辑器中打开。这种格式的唯一好处是有一个易于编辑的文件,通常不会使用,因为文件大小太大了。
如何选择正确的格式取决于我们要怎样处理资源。
如果希望能够在导出格式文件后还能更改纹理或灯光的坐标,最好使用默认的glTF。
如果只想一个模型对应一个文件,并且不关心去修改资源内容,那么最好选择glTF-Binary二进制格式文件。
这俩种情况下,还得决定是否要使用Draco压缩。
在three.js中加载GLTF文件,必须使用GLTF加载器,因此要引入GLTFLoader
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
实例化GLTF加载器
const gltfLoader = new GLTFLoader()
console.log一下康康里面有些什么信息
gltfLoader.load('/models/Duck/glTF/Duck.gltf', gltf => {
console.log(gltf)
})
可以看到有许多元素,其中最重要的便是scene场景属性,因为导出的模型只有一个场景,这个场景便包含了我们需要的一切信息。
THREE.Group: scene
└───Array: children
└───THREE.Object3D
└───Array: children
├───THREE.PerspectiveCamera
└───THREE.Mesh
其中网格就是我们的小黄鸭模型。
想要把小黄鸭添加到我们的场景中,有多种办法可以实现:
因为这次的模型结构很简单,所以我们会采用上面第二个方法。
下面三种glTF格式都可以加载出来小黄鸭模型,只有glTF-Draco加载报错,下面会细讲。
gltfLoader.load('/models/Duck/glTF/Duck.gltf', gltf => { //Default glTF
scene.add(gltf.scene.children[0])
})
// 或者
gltfLoader.load(
'/models/Duck/glTF-Binary/Duck.glb', // glTF-Binary
// 或者
gltfLoader.load(
'/models/Duck/glTF-Embedded/Duck.gltf', // glTF-Embedded
一样是从glTF model samples拿的模型
gltfLoader.load('/models/FlightHelmet/glTF/FlightHelmet.gltf', gltf => {
scene.add(gltf.scene.children[0])
})
可以发现没有一个完整的头盔模型,只有其中的一部分。这是因为上面的代码只是将已加载场景的第一个子元素给添加到我们的场景中。试下用for循环解决。
for(const child of gltf.scene.children)
{
scene.add(child)
}
可以发现循环后的结果虽然可以看到有更多的模型元素,但一样不是完整的模型,而且更糟糕的是每当我们刷新页面,都会得到模型的不同部分。
问题出在于当我们把scene.children
数组中的子元素从一个场景移到另一个场景的时候,它会自动从被移除的场景中删除掉,意味着我们循环的数组长度变小了。
当我们添加第一个对象时,它会从原来的场景中移除,然后第二个对象便会移动到替补上去。因此这里我们可以采用while循环来添加模型网格
gltfLoader.load('/models/FlightHelmet/glTF/FlightHelmet.gltf', gltf => {
while (gltf.scene.children.length) {
scene.add(gltf.scene.children[0])
}
})
现在我们就得到一个完整的头盔了。当然也可以用下面的方式实现,将子元素提取出来放到一个新数组里面,这样就不会改变原来的数组了。
const children = [...gltf.scene.children]
for(const child of children){
scene.add(child)
}
或者更简单粗暴的方式,因为gltf.scene
是一个组Group
,我们可以直接把组添加到场景里面
gltfLoader.load('/models/FlightHelmet/glTF/FlightHelmet.gltf', gltf => {
scene.add(gltf.scene)
})
使用glTF-Draco版本加载模型报错,因此我们需要向GLTFLoader提供一个DracoLoader实例,以便它能够加载压缩文件。
gltfLoader.load('/models/Duck/glTF-Draco/Duck.gltf', gltf => {
scene.add(gltf.scene)
})
引入DRACOLoader并实例化
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
const dracoLoader = new DRACOLoader()
three.js提供了Draco解码器的代码,可以去/node_modules/three/examples/js/libs/
找到该文件夹,复制整个文件夹到static
文件夹下,之后通过.setDecoderPath()
方法将文件提供给dracoLoader。
最后,我们可以使用setDRACOLoader()
将DRACOLoader实例dracoLoader提供给GLTFLoader实例gltfLoader:
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/')
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load('/models/Duck/glTF-Draco/Duck.gltf', gltf => {
scene.add(gltf.scene)
})
之后便能看到小黄鸭模型了
虽然我们可能会觉得使用Draco压缩是个双赢局面,但实际上并非如此。
确实它会让几何体更轻量,但首先要使用的时候必须加载DracoLoader类和解码器。其次,我们计算机解码一个压缩文件需要时间和资源,这可能会导致页面打开时有短暂冻结,即便我们使用了worker和WebAssembly。
因此我们必须根据实际来决定使用什么解决方案。如果一个模型具有100kb的几何体,那么则不需要Draco压缩,但是如果我们有MB大小的模型要加载,并且不关心在开始运行时有些许页面冻结,那么便可能需要用到Draco压缩。
因为加载出来的狐狸模型非常大,因此需要对其进行缩放
gltfLoader.load('/models/Fox/glTF/Fox.gltf', gltf => {
gltf.scene.scale.set(0.025,0.025,0.025)
scene.add(gltf.scene)
})
加载的gltf
对象包含由多个AnimationClip
组成的animations
属性,为此我们需要创建一个动画混合器AnimationMixer
,AnimationMixer类似于可以包含一个或多个AnimationClips对象的播放器
let mixer = null
gltfLoader.load('/models/Fox/glTF/Fox.gltf', gltf => {
// 创建混合器并传入gltf.scene
mixer = new THREE.AnimationMixer(gltf.scene)
// 使用clipAction()方法将索引为0的AnimationClip添加到混合器中
// 这个方法会返回一个AnimationAction
const action = mixer.clipAction(gltf.animations[0])
// 使用play()方法调用这个AnimationAction
action.play()
gltf.scene.scale.set(0.025, 0.025, 0.025)
scene.add(gltf.scene)
})
为看到动画我们必须回到动画函数里面在每一帧更新混合器
const tick = () => {
......
//更新混合器
if (mixer !== null) {
mixer.update(deltaTime)
}
......
}
当然可以试下索引1和2的AnimationClip
three.js有其自己的在线编辑器https://threejs.org/editor/。
就像一款3D软件,只不过能够在线运行但是功能较少,可以创建基本的几何体、灯光、材质等。
你可以导入自己的模型,因此这是测试自己的模型是否正常工作的一个好方法。但是要注意,只能测试由一个文件组成的模型,像是glTF-Binary或glTF-Embedded版本的小黄鸭。
在菜单中添加环境光和平行光后便能看清楚小黄鸭了
可以从这个网站获取各种模型,有收费也有免费的
https://sketchfab.com/feed
比如在里边下载一个gltf格式的模型,你会获得一个压缩包。解压缩后按照上边教程加载gltf文件即可。
由于网上模型包含网格较多,很难找出想要的部分网格,那么可以先去Three.js Editor,点击左上角File->import
,选中你下载的模型的压缩包即可将模型加载出来,这时你可以选中模型慢慢找你要的网格的名称。
之后再回到traverse()
方法中去操作你要的网格:
loadModel(gltfLoader) { gltfLoader.load( '/static/fictional_supercar_-_v12_goblin/scene.gltf', (gltf) => { gltf.scene.children[0].scale.set(1, 1, 1) gltf.scene.children[0].position.setY(0.1) gltf.scene.children[0].traverse((child) => { if (child.isMesh) { if (child.name === 'clearcoat_clearcoat_0') { child.material.color.set(this.debugParamters.clearcoatColor) this.gui .addColor(this.debugParamters, 'clearcoatColor') .name('车罩颜色') .onChange(() => { child.material.color.set(this.debugParamters.clearcoatColor) }) } if (child.name === 'car_body_car_body_0') { child.material.color.set(this.debugParamters.bodyColor) child.material.emissive = child.material.color child.material.emissiveMap = child.material.map this.gui .addColor(this.debugParamters, 'bodyColor') .name('车壳颜色') .onChange(() => { child.material.color.set(this.debugParamters.bodyColor) }) } } }) this.scene.add(gltf.scene.children[0]) }, (xhr) => { console.log((xhr.loaded / xhr.total) * 100 + '%loaded') } ) }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。