24.Three.js解决方案之加载.gLTF模型

24 Three.js解决方案之加载.gltf模型

上一章节,我们讲解了加载.obj模型,本文将讲解加载.gltf模型。

注:虽然我们标题写的是 “加载.gltf模型”,但更加准确地说法应该是 “加载gtTF文件”


首先我们先回顾一下.obj文件格式的模型一些特征:文件格式简单(纯文本)、除模型外无法提供其他场景元素(例如摄像机、灯光等)

本文要讲解的.gltf格式文件可以包含的数据内容和类型要比.obj多很多。


常见3D文件格式 和gltf的区别

我们先将常见的3D文件格式进行划分

1(原始文件)

3D建模软件本身特有的、原始的文件格式,例如:

  1. Blender对应的是.blend
  2. 3D Max对应的是.max
  3. Maya对应的是.ma
  4. C4D对应的是.c4d

2(中转文件)

多个3D建模软件彼此都可以打开,能够读取的文件格式,例如:

  1. .obj
  2. .dae
  3. .fbx

所谓“中转”,是指这些格式的作用实际上相当于将某个模型从A软件 导出 然后再B软件中可以打开并读取。


3(特定格式)

某些3D应用独有的文件格式。

例如王者荣耀这款游戏中3D模型就可能是自己独有的文件格式。


4(传输格式)

这里的 “传输” 是英文单词 “Transmission” 的翻译

gltf就属于传输格式类型的文件。gltf格式可以做到其他格式都无法做到的事情。


GLTF文件格式简介

GLTF是英文:Graphics Language Transmission Format的缩写

WebGL、OpenGL中的 “GL” 和GLTF中的 “GL” 是相同的单词。

GLTF中文全称为:图形语言传输格式

GLTF本身就是由OpenGLVulkan背后的3D图形标准组织Khronos定义的。

所以你可以想象得到,gltf本身就是为了网络传输、浏览器渲染3D而生的。

关于更多gltf信息,可以查看其官网:https://www.khronos.org/gltf/


GLTF的支持度:几乎所有的Web 3D图形框架都支持GLTF

除了Three.js框架外 ,其他3D JS引擎框架也都支持GLTF


gltf优点1:体积小,便于传输

gltf文件中模型的数据都以二进制存储,当下载(使用) gltf文件时可以将这些二进制数据直接在GPU中使用。

反观vrml.obj.dae等格式,他们是将数据存储为文本(例如纯文本或JSON格式),也就是说GPU在读取这些模型文件时还需要进行文本解析。

在文件体积方面,通常相同的模型顶点数据如果用文本形式存储,要比二进制存储体积大35倍。


gltf优点2:直接渲染

gltf文件中模型的数据是直接要渲染的,而不是要再次编辑的。

换句话说gltf文件中的模型是不可以再次编辑的

而其他类型的文件,例如.obj中模型是可以在Three.js中加载完成后二次编辑的

你可以简单的把gltf想象成jpg图片,而其他格式的3D文件是PSD文件,当我们仅仅是为了看到图片,无需编辑该图片时,肯定是.jpg图片体积小,打开速度快。

正因为是不可编辑,所以一些对于渲染而言不重要的数据通常都已被删除,例如多边形都已转化为三角形。


gltf优点3:内嵌材质信息

gltf文件中模型的材质信息是被内嵌进去。

请注意我们这里说的是 “材质信息”,也就是相当于.obj对应的.mtl中的数据,但是对于纹理图片资源(xxx.jpg)本身来说,并不会内嵌进去。

所以,这里隐含的一个事情就是,我们依然需要将.gltf文件对应的纹理图片资源.jpg放在pulic目录中。


结论:gltf格式非常有针对性,是专门为渲染而设计的,文件体积小,且GPU读取快速。

因此,推荐使用gltf格式。


Blender中导出gltf文件

讲了这么多gltf文件的优点,那么我们打开之前创建的hello.blend文件,导出一下gltf文件看看。

导出步骤:

  1. 打开hello.blend

  2. 文件>导出> glTF 2.0(.glb/.gltf)

  3. 在弹窗对话框中,使用默认导出项,我们直接点 导出

    尽管我们使用的是默认导出项,但是还请你留意一下这几项内容:

    包括:选定的物体(未勾选)、自定义属性(未勾选)、相机(未勾选)、精确灯光(未勾选)

    变换Y向上(已勾选)

    几何数据:应用修改器(未勾选)UV(已勾选)、法向(已勾选)、切向(未勾选)、顶点色(已勾选)、材质(导出)、图像(自动)、压缩(未勾选)

    动画:动画(已勾选)、形态键(已勾选)、蒙皮(已勾选)

    尽管我们创建的hello.blend中并未设置任何动画,你可以选择取消动画相关的勾选


此时去导出目录里,我们会发现多出来了一个 hello.glb 的文件。

特别强调:由于纹理图片资源metal_texture.jpg本身就在目录中,所以我们只是从直观上感觉多出了1个文件而已。


.glb ?不是.gltf

~,别着急,我们补充一下GLTF格式的知识。


GLTF是一种3D文件格式规范,但是却有3种表现形式

3种表现形式分别为:分离式、二进制、嵌入式

1种表现形式(分离式).gltf + .bin +纹理贴图资源(.jpg、.png)

  1. gltf:3D场景的所有概要信息,包括灯光、纹理贴图等信息

    该文件的内容具体形式为JSON

  2. .bin:模型的二进制数据

  3. 纹理贴图资源:这个就不多说了,就是纹理图片xxx.jpg.png


2种表现形式(二进制):.glb

.glb:包含场景所有的信息的二进制数据。

.glb === .gltf + .bin +纹理图片资源


3种表现形式(嵌入式):.gltf

.gltf:以JSON形式保存所有场景信息数据,包括材质和纹理信息。

这种形式由于文件内容是json,因此是可以通过文本再次编辑的


Blender默认导出glTF 2.0格式时,采用的是.glb后缀形式。

想要更改成别的导出形式,我们可以在Blender导出项 格式下拉框中更改为 “.gltf分离(.gltf + .bin +纹理)” 或 “glTF嵌入式(.gltf)"

那么此时导出的文件格式就是.gltf后缀形式。


3种形式的对比:

以下纯粹是我个人的观点,仅供参考

比较常见的是前2种:分离式(.gltf + .bin +纹理)、二进制形式(.glb)

如果你的项目中,模型数据不会发生变化,但是纹理贴图可能容易发生变化,那么可以选择 分离式的。

分离式的贴图资源本身就是单独存在的,因此方便替换修改。

如果你是要发送给其他人使用、且不会发生材质变更的,则可以采用.glb形式的。

由于所有数据都只在.glb文件中,就1个文件也利于文件发送。


补充一点:有一个网站 https://gltf-viewer.donmccurdy.com/ ,他可以提供.glb文件在线预览。

同时在NPM上面,有很多针对.glb.gltf格式互转的工具包,例如:gltf-import-export


对于Three.js来说,加载glTF格式的文件,无论哪种形式,均支持。


使用GLTFLoader加载glTF文件的示例

Three.js中负责加载glTF格式文件的加载器为GLTFLoader

用法和之前OBJLoader用法完全相同,废话不多说,直接看代码。


我们先加载.glb格式的文件,代码如下:

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中包含的数据有:

  1. gltf.animations; // Array<THREE.AnimationClip>
  2. gltf.scene; // THREE.Group
  3. gltf.scenes; // Array<THREE.Group>
  4. gltf.cameras; // Array<THREE.Camera>
  5. gltf.asset; // Object

我们此刻只是将gltf.scene添加到了场景中而已,其他数据暂时并未使用到。


和加载.glb类似,如果我们的3D数据文件为 分离式的.gltf,则将上述代码修改为:

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/目录中。


至此,加载.gltf文件讲解完成。

就这?明明就几行代码的事情,为什么还要花这样大的篇幅来讲解.obj.glb、gltf ?

答:要想学得深入,就一定要知道原理,知道objgltf的差异,知其然也要知其所以然。


我这里提供几个上找到的glTF文件资源,方便自己练习使用。

一个黄色的小鸭子:

  1. https://vr.josh.earth/assets/models/duck/duck.gltf
  2. https://vr.josh.earth/assets/models/duck/duck.bin
  3. https://vr.josh.earth/assets/models/duck/duck.png

一个简易3D社区

https://threejsfundamentals.org/threejs/resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf

这个小区模型比较大,你需要适当调整一下镜头参数,才可以看清楚全貌


一个酷酷的头盔

https://cdn.khronos.org/assets/api/gltf/DamagedHelmet.glb


一个宇航员

https://modelviewer.dev/shared-assets/models/Astronaut.glb

真的好酷!


谷歌开源的一个JS库:model-viewer

在搜索glTF相关文章时,我无意中发现另外谷歌公司开源的一个JS项目: model-viewer

项目Github地址:https://github.com/google/model-viewer

项目官网:https://modelviewer.dev/

项目介绍:Easily display interactive 3D models on the web & in AR

简单来说就是:在WebAR中,一个简单的用来显示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 ,然后可以使用 标签插入模型渲染显示标签。

简直和插入图片标签 没啥区别。


交互效果:除了可以渲染出3D模型文件,还默认配备有类似OrbitControls相同的交互效果。


兼容性:目前 苹果浏览器Safari、火狐Firefox并不支持。


至此,关于如何加载glTF文件已讲解完毕。

但是有一点我们没有提到,就是使用glTF中自带的灯光、镜头、动画等内容。

由于目前我还不会在Blender创建动画,所以这一块我们暂且保留,等待以后有机会再继续学习。


Three.js中,还有很多其他文件格式的加载器,我们就不逐个讲解了,具体的可以查阅官方文档。


你以为本文结束了?没有!

在上面示例中,我们实际上漏掉了一个非常重要的知识点:加载被压缩过的.glb文件


glTF文件压缩和加载(解压)——Draco

在本文的示例中,所演示加载的.glb文件是我自己在Blender中创建导出的。

如同图片文件一样,也有专门针对.glb文件压缩的工具,最为著名的就是谷歌公司开源的:draco

Draco简介

Draco是一种库,用于压缩和解压缩3D几何网格(geometric mesh)和 点云(point cloud)

draco官网:https://google.github.io/draco/

draco源码:https://github.com/google/draco


draco底层是使用c++编写的。

draco可以在不牺牲模型效果的前提下,将.glb文件压缩体积减小很多。

就好像将普通文件压缩成.zip一样

至于文件减少多少,这个暂时没有查询到


Draco使用流程是:

  1. 使用Draco将模型压缩,最终压缩后的文件格式为.drc.glb

    Draco可以压缩众多3D格式文件,.glb仅仅是其中一种

  2. .glb文件内部有一个特殊字段,用来表述本文件是否经过了draco压缩

  3. 当客户端(JS)使用GLTFLoader去加载某个.glb文件时会去读取该标识

  4. 若判断该.glb文件未被压缩则直接进行加载和解析

  5. 若判断该.glb文件是被draco压缩过的,则会尝试调用draco解压类,下载.glb文件的同时进行解压,最终将下载、解压后的.glb数据传递给GLTFLoader使用

    这就引申出来一个事情:我们需要提前将负责draco解压的类传递给GLTFLoader,具体如何做请看后面的讲解。


如何使用Draco压缩.glb文件?

具体如何操作实现,暂时我也没有学习,先搁置一下。

敬请期待以后的更新


如何在Three.js中加载压缩过的.glb文件?

关于Draco的介绍,可以查看Three.js对于Draco的介绍描述:

https://github.com/mrdoob/three.js/tree/dev/examples/js/libs/draco

Three.js源码包中draco针对gltf文件的解压文件库:

  1. draco/目录下有4个文件:draco_decoder.js、draco_decoder.wasm、draco_encoder.js、draco_wasm_wrapper.js

  2. draco/gltf/目录下面同样有4个文件

    请注意draco/draco/gltf/目录下的4个文件虽然是名字一样,但是他们内容并不相同。

分别解释一下这4个文件:

  1. draco_decoder.js

    draco解压(解码)相关js

  2. draco_decoder.wasm

    .wasm文件是WebAssembly解码器

    关于WebAssembly更多知识,请执行查阅:https://www.wasm.com.cn/

  3. draco_encoder.js

    draco压缩(编码)相关js

  4. draco_wasm_wrapper.js

    用于封装.wasm解码器的js

重点来了…


1步:拷贝draco文件到项目public

我们将Three.jsexamples/js/libs/draco目录拷贝到React项目的public目录中。

draco属于第3方库,我们目前暂时采用拷贝到public目录中这种形式

请记得一定拷贝的是draco/,其中包含draco/gltf/目录

2步:实例化一个DRACOLoader,并传递给GLTFLoader

关于DRACOLoader的详细解释,请参考官方文档:

https://threejs.org/docs/#examples/zh/loaders/DRACOLoader


我们将之前GLTFLoader的代码修改如下:

+	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)
	})

下面就针对上面4行核心代码进行解释说明:

  1. const dracoLoader = new DRACOLoader()

    实例化一个DRACOLoader

  2. dracoLoader.setDecoderPath('./examples/js/libs/draco/')

    设置dracoLoader应该去哪个目录里查找 解压(解码)文件

  3. dracoLoader.setDecoderConfig({ type: 'js' })

    设置dracoLoader的配置项

  4. gltfLoader.setDRACOLoader(dracoLoader)

    dracoLoader传递给gltfLoader,供gltfLoader使用

至此,结束!


虽然draco非常复杂,但是对于我们使用者而言却很简单,仅仅上面4行代码即可实现加载被draco压缩过的.glb文件。


加载.drc模型文件

在上面示例中,我们加载的是被draco压缩过的.glb文件。

那如果是被draco压缩过的.drc文件呢?

答:更加简单,直接使用DRACOLoader即可。


DRACOLoader使用示例代码如下:

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 );
	}
}

补充说明:修改模型位置偏差

无论加载.obj文件,还是本章讲解的加载.gltf文件,假设模型在建模软件中位置中心并不是原点,而是非常偏远的位置。

那么文件加载完成后,将模型添加到场景中,模型的位置并不在场景视角的中心位置,如果位置过于偏远,甚至有可能根本看不见模型。

我们可以通过以下方式,计算模型的位置偏差,并修正模型的位置,使其出现在视野中心位置。

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的介绍请执行查阅官方文档。


下一章节,我们要学习如何添加 场景背景,呵, VR看房效果要来了!

上一页
下一页