24.Three.js 解决方案之加载.gLTF 模型
24 Three.js 解决方案之加载.gltf 模型
上一章节,我们讲解了加载
注:虽然我们标题写的是 “加载
.gltf 模型”,但更加准确地说法应该是 “加载gtTF 文件”
首先我们先回顾一下
本文要讲解的
常见3D 文件格式 和gltf 的区别
我们先将常见的
第
Blender 对应的是.blend 3D Max 对应的是.max Maya 对应的是.ma C4D 对应的是.c4d
第
多个
- .obj
- .dae
- .fbx
所谓“中转”,是指这些格式的作用实际上相当于将某个模型从
A 软件 导出 然后再B 软件中可以打开并读取。
第
某些
例如王者荣耀这款游戏中
第
这里的 “传输” 是英文单词 “Transmission” 的翻译
GLTF 文件格式简介
WebGL、
OpenGL 中的 “GL” 和GLTF 中的 “GL” 是相同的单词。
GLTF 本身就是由OpenGL 和Vulkan 背后的3D 图形标准组织Khronos 定义的。
所以你可以想象得到,
关于更多
除了
Three.js 框架外 ,其他3D JS 引擎框架也都支持GLTF
反观
在文件体积方面,通常相同的模型顶点数据如果用文本形式存储,要比二进制存储体积大
换句话说
gltf 文件中的模型是不可以再次编辑的
而其他类型的文件,例如
.obj 中模型是可以在Three.js 中加载完成后二次编辑的
你可以简单的把
gltf 想象成jpg 图片,而其他格式的3D 文件是PSD 文件,当我们仅仅是为了看到图片,无需编辑该图片时,肯定是.jpg 图片体积小,打开速度快。
正因为是不可编辑,所以一些对于渲染而言不重要的数据通常都已被删除,例如多边形都已转化为三角形。
请注意我们这里说的是 “材质信息”,也就是相当于
所以,这里隐含的一个事情就是,我们依然需要将
.gltf 文件对应的纹理图片资源.jpg 放在pulic 目录中。
结论:
因此,推荐使用
在Blender 中导出gltf 文件
讲了这么多
导出步骤:
-
打开
hello.blend -
文件
> 导出> glTF 2.0(.glb/.gltf) -
在弹窗对话框中,使用默认导出项,我们直接点
导出
尽管我们使用的是默认导出项,但是还请你留意一下这几项内容:
包括
:选定的物体( 未勾选) 、自定义属性( 未勾选) 、相机( 未勾选) 、精确灯光( 未勾选) 变换
:Y 向上( 已勾选) 几何数据
: 应用修改器( 未勾选) 、UV( 已勾选) 、法向( 已勾选) 、切向( 未勾选) 、顶点色( 已勾选) 、材质( 导出) 、图像( 自动) 、压缩( 未勾选) 动画
:动画( 已勾选) 、形态键( 已勾选) 、蒙皮( 已勾选) 尽管我们创建的
hello.blend 中并未设置任何动画,你可以选择取消动画相关的勾选
此时去导出目录里,我们会发现多出来了一个 hello.glb
的文件。
特别强调:由于纹理图片资源
metal_texture.jpg 本身就在目录中,所以我们只是从直观上感觉多出了1 个文件而已。
.glb ?不是
额
GLTF 是一种3D 文件格式规范,但是却有3 种表现形式
第
-
gltf:
3D 场景的所有概要信息,包括灯光、纹理贴图等信息该文件的内容具体形式为
JSON -
.bin:模型的二进制数据
-
纹理贴图资源:这个就不多说了,就是纹理图片
xxx.jpg 或.png
第
.glb:包含场景所有的信息的二进制数据。
.glb === .gltf + .bin + 纹理图片资源
第
.gltf:以
这种形式由于文件内容是
json ,因此是可以通过文本再次编辑的
想要更改成别的导出形式,我们可以在格式
下拉框中更改为 “
那么此时导出的文件格式就是
以下纯粹是我个人的观点,仅供参考
比较常见的是前
如果你的项目中,模型数据不会发生变化,但是纹理贴图可能容易发生变化,那么可以选择 分离式的。
分离式的贴图资源本身就是单独存在的,因此方便替换修改。
如果你是要发送给其他人使用、且不会发生材质变更的,则可以采用
由于所有数据都只在
.glb 文件中,就1 个文件也利于文件发送。
补充一点:有一个网站 https://gltf-viewer.donmccurdy.com/ ,他可以提供
同时在
对于
使用GLTFLoader 加载glTF 文件的示例
在
用法和之前
我们先加载
src/components/hello-gltfloader
由于
.glb 文件是单独1 个存在,所以我们这次可以将hello.glb 文件放在src/assces/model/ 目录下了。
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"
...
const loader = new GLTFLoader()
loader.load(require('@/assets/model/hello.glb').default, (gltf) => {
scene.add(gltf.scene)
})
请注意当加载完成后,执行的是
scene.add(gltf.scene) 加载完成得到的
gltf 中包含的数据有:
- gltf.animations; // Array<THREE.AnimationClip>
- gltf.scene; // THREE.Group
- gltf.scenes; // Array<THREE.Group>
- gltf.cameras; // Array<THREE.Camera>
- gltf.asset; // Object
我们此刻只是将
gltf.scene 添加到了场景中而已,其他数据暂时并未使用到。
和加载
loader.load('./model/hello.gltf', (gltf) => {
scene.add(gltf.scene)
})
注意:我们只需将
hello.gltf 传递给loader 即可,loader 会读取.gltf 中的数据,自动去加载对应的hello.bin 和 纹理图片hello.jpg 。
由于牵扯到不同的文件
webpack 编译,所以我们选择将.gltf 、.bin、.jpg 文件放在src/public/ 目录中。
至此,加载
就这?明明就几行代码的事情,为什么还要花这样大的篇幅来讲解
答:要想学得深入,就一定要知道原理,知道
我这里提供几个上找到的
一个黄色的小鸭子:
- https://vr.josh.earth/assets/models/duck/duck.gltf
- https://vr.josh.earth/assets/models/duck/duck.bin
- https://vr.josh.earth/assets/models/duck/duck.png
一个简易
这个小区模型比较大,你需要适当调整一下镜头参数,才可以看清楚全貌
一个酷酷的头盔
https://cdn.khronos.org/assets/api/gltf/DamagedHelmet.glb
一个宇航员
https://modelviewer.dev/shared-assets/models/Astronaut.glb
真的好酷!
谷歌开源的一个JS 库:model-viewer
在搜索
项目
项目官网:https://modelviewer.dev/
项目介绍:Easily display interactive 3D models on the web & in AR
简单来说就是:在
Web 或AR 中,一个简单的用来显示3D 模型的JS 库。
具体用法:
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<model-viewer src="shared-assets/models/Astronaut.glb" alt="A 3D model of an astronaut" auto-rotate camera-controls></model-viewer>
确实够简单了,就是引入
viewer ,然后可以使用标签插入模型渲染显示标签。 简直和插入图片标签
没啥区别。
交互效果:除了可以渲染出
兼容性:目前 苹果浏览器
至此,关于如何加载
但是有一点我们没有提到,就是使用
由于目前我还不会在
在
你以为本文结束了?没有!
在上面示例中,我们实际上漏掉了一个非常重要的知识点:加载被压缩过的
glTF 文件压缩和加载( 解压) ——Draco
在本文的示例中,所演示加载的
如同图片文件一样,也有专门针对
Draco 简介
就好像将普通文件压缩成
.zip 一样
至于文件减少多少,这个暂时没有查询到
Draco 使用流程是:
-
使用
Draco 将模型压缩,最终压缩后的文件格式为.drc 或.glb Draco 可以压缩众多3D 格式文件,.glb 仅仅是其中一种 -
在
.glb 文件内部有一个特殊字段,用来表述本文件是否经过了draco 压缩 -
当客户端
(JS) 使用GLTFLoader 去加载某个.glb 文件时会去读取该标识 -
若判断该
.glb 文件未被压缩则直接进行加载和解析 -
若判断该
.glb 文件是被draco 压缩过的,则会尝试调用draco 解压类,下载.glb 文件的同时进行解压,最终将下载、解压后的.glb 数据传递给GLTFLoader 使用这就引申出来一个事情:我们需要提前将负责
draco 解压的类传递给GLTFLoader ,具体如何做请看后面的讲解。
如何使用Draco 压缩.glb 文件?
具体如何操作实现,暂时我也没有学习,先搁置一下。
敬请期待以后的更新
如何在Three.js 中加载压缩过的.glb 文件?
关于
https://github.com/mrdoob/three.js/tree/dev/examples/js/libs/draco
-
draco/ 目录下有4 个文件:draco_decoder.js、draco_decoder.wasm、draco_encoder.js、draco_wasm_wrapper.js -
draco/gltf/ 目录下面同样有4 个文件请注意
draco/ 和draco/gltf/ 目录下的4 个文件虽然是名字一样,但是他们内容并不相同。
分别解释一下这
-
draco_decoder.js
draco 解压( 解码) 相关js -
draco_decoder.wasm
.wasm 文件是WebAssembly 解码器关于
WebAssembly 更多知识,请执行查阅:https://www.wasm.com.cn/ -
draco_encoder.js
draco 压缩( 编码) 相关js -
draco_wasm_wrapper.js
用于封装
.wasm 解码器的js
重点来了…
第1 步:拷贝draco 文件到项目public 中
我们将
draco 属于第3 方库,我们目前暂时采用拷贝到public 目录中这种形式请记得一定拷贝的是
draco/ ,其中包含draco/gltf/ 目录
第2 步:实例化一个DRACOLoader ,并传递给GLTFLoader
关于
DRACOLoader 的详细解释,请参考官方文档:
我们将之前
+ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
const gltfLoader = new GLTFLoader()
+ const dracoLoader = new DRACOLoader()
+ dracoLoader.setDecoderPath('./examples/js/libs/draco/')
+ dracoLoader.setDecoderConfig({ type: 'js' })
+ gltfLoader.setDRACOLoader(dracoLoader)
gltfLoader.load('./model/vivo.glb', (gltf) => {
scene.add(gltf.scene)
})
下面就针对上面
-
const dracoLoader = new DRACOLoader()
实例化一个
DRACOLoader -
dracoLoader.setDecoderPath('./examples/js/libs/draco/')
设置
dracoLoader 应该去哪个目录里查找 解压( 解码) 文件 -
dracoLoader.setDecoderConfig({ type: 'js' })
设置
dracoLoader 的配置项 -
gltfLoader.setDRACOLoader(dracoLoader)
将
dracoLoader 传递给gltfLoader ,供gltfLoader 使用
至此,结束!
虽然
加载.drc 模型文件
在上面示例中,我们加载的是被
那如果是被
答:更加简单,直接使用
const loader = new DRACOLoader();
loader.setDecoderPath( '/examples/js/libs/draco/' );
loader.preload();
loader.load('./xxx/model.drc',
function ( geometry ) {
const material = new THREE.MeshStandardMaterial( { color: 0x606060 } );
const mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
}
}
补充说明:修改模型位置偏差
无论加载
那么文件加载完成后,将模型添加到场景中,模型的位置并不在场景视角的中心位置,如果位置过于偏远,甚至有可能根本看不见模型。
我们可以通过以下方式,计算模型的位置偏差,并修正模型的位置,使其出现在视野中心位置。
const loader = new GLTFLoader()
loader.load('./model/lddq.gltf', (gltf) => {
const group = gltf.scene
const box = new Three.Box3().setFromObject(group)
const center = box.getCenter(new Three.Vector3())
group.position.x += (group.position.x - center.x)
group.position.y += (group.position.y - center.y)
group.position.z += (group.position.z - center.z)
scene.add(group)
})
Box3 的介绍请执行查阅官方文档。
下一章节,我们要学习如何添加 场景背景,呵,