Skip to content

25. 如何加载外部模型

Published:

前面我们用 Three.js 的几何体画过房子:

image.png

就这么一个简单的物体都要画好久,那一些更复杂的,比如人、动物、汽车、设备等物体呢?

这种就不是用 Three.js 的 api 画了,而是用专门的建模软件,比如 Blender、3DMax 等。

而且会有专门的建模人员来画,最后导出模型给程序员用。

就像有些图你会找设计来画,然后导出 jpg、png 给你用一样,在 3D 模型领域,就是美术在建模软件导出 gltf 格式的文件给你用。

image.png

比如 Blender 里就可以导出 gltf 格式的文件:

image.png

GLTF 格式是最常用的模型的格式,支持的特性是最多的。

通过 GLTFLoader 加载到网页里,就是一个直接可用的网格模型了。

Three.js 的源码仓库里就有很多现成的 gltf 模型:

image.png

点击这里下载:

image.png

2025-03-30 19.54.14.gif

我们下载 House.gltf 和 House.bin 文件。

.gltf 是入口文件,.bin 是存储顶点信息的文件。

然后创建项目:

mkdir gltf-model
cd gltf-model
npm init -y

image.png

安装 ts 类型:

npm install --save-dev @types/three

创建 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body {
            margin: 0;
        }
    </style>
</head>
<body>
    <script type="importmap">
    {
        "imports": {
            "three": "https://esm.sh/three@0.174.0/build/three.module.js",
            "three/addons/": "https://esm.sh/three@0.174.0/examples/jsm/"
        }
    }
    </script>
    <script type="module" src="./index.js"></script>
</body>
</html>

index.js

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 light = new THREE.DirectionalLight(0xffffff);
light.position.set(100, 100, 100);
scene.add(light);

const axesHelper = new THREE.AxesHelper(1000);
scene.add(axesHelper);

const width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 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,这些都和前面一样。

不一样的是这次网格模型不再是自己画,而是直接加载外部模型:

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("./Horse.gltf", function (gltf) {
    mesh.add(gltf.scene);
})

export default mesh;

GLTFLoader 在 loaders 目录下,需要单独引入。

创建 GLTFLoader 对象,加载刚才下载的 gltf 文件(这里要把 .gltf、.bin 都复制过来)。

它回调函数里拿到 gltf.scene 就是一些网格模型,我们把它放到 Group 里,加到我们的 Scene 里。

跑一下:

image.png

image.png

可以看到这个网格模型了,就是比较小。

我们把相机位置调一下:

image.png

image.png

GLTFLoader 那个回调函数的对象里都有什么呢?

我们打印下看看:

image.png

image.png

可以看到 scene 是一个 Group,里面有一个网格模型,就是马,它的 children 就是组成马的各个部分

image.png

而 animations 是一些动画,比如跑、跳骨骼动画,我们后面再讲

image.png

我们看看一共有几个网格模型:

image.png

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

image.png

打印了 2 个。

这两个网格模型都是马的哪部分呢?

image.png

网格模型都有 geometry 和 material,我们改下它的材质颜色试试。

这两个 Mesh 的 name 分别叫 Cylinder 和 Cylinder_1 image.png

image.png

我们分别设置不同的材质颜色:

image.png

gltf.scene.traverse(obj => {
    if(obj.isMesh) {
        console.log('mesh', obj);
        if(obj.name === 'Cylinder') {
            obj.material.color = new THREE.Color('white');
        } else if(obj.name === 'Cylinder_1') {
            obj.material.color = new THREE.Color('pink');
        }
    }
});

image.png

可以看到,这两个 mesh 分别是马的身体和马鬃、马尾、马蹄。

这种不规则的几何体就是一堆顶点构成了三角形,然后形成的这种形状:

image.png

可以看到有 1052 个顶点

image.png

顶点索引有 1512 个,也就是 504 个三角形构成的身体:

image.png

我们可以用线框来展示下:

image.png

const obj = gltf.scene.getObjectByName('Cylinder');
obj.material.wireframe = true;

这次不遍历了,直接用 getObjectByName 根据 name 查找。

image.png

它用的反光的材质,所以背光的看不到,我们换成 MeshBasicMaterial。

image.png

obj.material = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    wireframe: true
})

2025-03-30 20.49.27.gif

可以看到,确实是一堆顶点构成了一些三角形,然后构成了马身体形状的几何体。

给我们这些顶点数据,我们自己用 BufferGeometry 画也行。

其实加载模型就是加载这些顶点数据,还有材质,以及其他的一些东西。

加载到场景 Scene 中之后,操作就和其他网格模型 Mesh 没区别了。

案例代码上传了小册仓库

总结

这节我们用 GLTFLoader 加载了一匹马的 gltf 模型。

一些复杂的物体用 Three.js 的几何体画不太现实,一般都是用建模软件画,然后导出 gltf 等格式的模型文件,我们在代码里用 GLTFLoader 加载进来。

scene 属性就是一个 Group,我们把它加到 Scene 里就可以了,它的 children 就是一些网格模型,可以随意修改材质等,和其他网格模型没区别。

比如可以用 traverse 遍历对象树,用 isMesh 方法判断是否是网格模型,或者用 getObjectByName 来按照名字查找等。

绝大多数 3D 场景都是要加载外部的网格模型的,不可能全部自己画,而模型文件格式里最常用的就是 gltf,后面的实战我们基本都会用到 gltf 模型。

评论