上节我们加载了一个 gltf 的模型,并用 Three.js 渲染了出来:

当时用了一个 .gltf 文件还有一个 .bin 文件:

其实 gltf 模型文件一共有三种形式:
- 只有 .gltf:用到的纹理图片以 base64 内嵌,顶点信息也是
- .gltf + .bin + .png/.jpg:.gltf 描述结构,用到的顶点信息等放在 .bin 里、纹理图片是单独的 jpg、png 文件
- .glb:用二进制的方式把所有资源到打包到一个文件里,体积更小
gltf 官方有个案例模型的仓库,我们可以去那看一下:

里面有很多 gltf 模型。
比如这个灯笼:

它就提供了 .glb 的模型,以及 .gltf + .bin + .png 的模型。
比如这个 CesiumMan 的模型:

提供了三种形式的文件:

.glb 和 .gltf + .bin + .jpg 或者 .gltf
我们用 Three.js 加载试一下:
npx create-vite gltf-structure

进入项目,安装依赖:
npm install
npm install --save three
npm install --save-dev @types/three
改下 src/main.js
import './style.css';
import * as THREE from 'three';
import {
OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';
const scene = new THREE.Scene();
scene.add(mesh);
const directionLight = new THREE.DirectionalLight(0xffffff);
directionLight.position.set(100, 100, 100);
scene.add(directionLight);
const helper = new THREE.AxesHelper(100);
scene.add(helper);
const width = window.innerWidth;
const height = window.innerHeight;
const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 10000);
camera.position.set(100, 100, 100);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height)
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
document.body.append(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
创建 Scene、Light、Camera、Renderer。
改下 style.css
body {
margin: 0;
}
把刚才那个模型下载下来(点右边的按钮下载):

下载这三个:

分别放到 public/gltf1、public/gltf2、public/gltf3 目录下:

然后创建 src/mesh.js
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
const mesh = new THREE.Group();
loader.load("./gltf1/CesiumMan.gltf", function (gltf) {
console.log(gltf);
mesh.add(gltf.scene);
})
export default mesh;
用 GLTFLoader 把 gltf 模型加载进来。
跑下试试:
npm run dev


加载出来了,就是太小了,我们放大下:

gltf.scene.scale.set(50, 50, 50);

遍历下看看有几个 mesh:

gltf.scene.traverse(obj => {
if(obj.isMesh) {
console.log(obj.name, obj);
}
})

只有一个,我们设置展示线框:

obj.material.wireframe = true;
obj.material.color.set('orange');

把颜色贴图去掉:

obj.material.map = null;

然后再加载另外两种格式的模型试试:

loader.load("./gltf2/CesiumMan.gltf", function (gltf) {
mesh.add(gltf.scene);
gltf.scene.scale.set(50, 50, 50);
gltf.scene.translateX(-50);
gltf.scene.traverse(obj => {
if(obj.isMesh) {
obj.material.wireframe = true;
obj.material.color.set('lightblue');
obj.material.map = null;
}
})
});
loader.load("./gltf3/CesiumMan.glb", function (gltf) {
mesh.add(gltf.scene);
gltf.scene.scale.set(50, 50, 50);
gltf.scene.translateX(50);
gltf.scene.traverse(obj => {
if(obj.isMesh) {
obj.material.wireframe = true;
obj.material.color.set('lightgreen');
obj.material.map = null;
}
})
});

也就是说,虽然存储的形式不一样,加载之后都是一样的。
那这三种文件形式都存了啥呢?
看一下就知道了:
全内联在 gltf 文件内是这样的:

可以看到二进制信息是以 base64 的方式写在文件里的。
这会导致文件特别大。
而 gltf + bin 是这样的:

.bin 存的是顶点信息。
再看下图片:
.gltf + .bin + .jpg 是这样的:

而 .gltf 内联是这样:

也就是说,虽然存储的都是纹理图片、顶点等信息,但一种是以文件形式单独存储,另一种则是全部 base64 内联。
而 .glb 也是全部内联,但它是二进制的,所以更小一点:

可以看到,同样是内联,.glb 小了 200k:


所以,.glb 或者 .gltf +.bin + .jpg 都是比较好的,而内联的 .gltf 在特定情况下也会用到。
案例代码上传了小册仓库。
总结
这节我们学习了 .gltf 模型的三种形式:
- .gltf:所有纹理图片、顶点信息都是 base64 内联在一个文件里
- .gltf + .bin + .jpg/.png:图片单独存在文件,顶点信息放在 .bin
- .glb:也是内联所有资源,但是二进制形式体积更小
后两种是用的最多的。
不管存储结构如何,GLTFLoader 加载进来之后都是一样的网格模型,没有区别。