Three.js で 3D モデル(glTF)を表示する

Three.js を久しぶりに触ってみたら、結構いろいろ変わっているらしく、現在は r127
昔使って遊んでいた時は確か r70 前後だった気がします。(どうやら 2015 年前後のことでした。)

そのころから比べると、どうやら取り扱える 3D モデルファイルの形式も増えたようです。
glTF という見覚えのない形式の取り扱いができるようになっていました。(r103 の頃から?)
これが非常に使いやすいものだったのでメモです。

適当に作ったモデルを表示しました。

参考

3d モデル作成

blander

現代の 3D モデル作成ではデファクトスタンダードでしょうか?

File -> Export -> glTF 2.0 (.glb/.gltf) を選択。
(.gltfは json 形式のファイルなのでテキストエディタで中身を参照できます。)

以下の設定を行って保存する。

メタセコイア

個人的にはメタセコイアの方が使いやすくて好きです。
保存するときに、glTF 2.0 binary(*.glb)を選んで保存すれば OK。

実装

vite で 環境用意

1
2
3
4
npm init @vitejs/app app --template vanilla
cd app
npm install
npm run dev

TypeScript 対応 1

tsconfig.jsonを以下のように用意しました。

tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"compilerOptions": {
"target": "esnext",
"lib": ["DOM", "DOM.Iterable", "esnext"],
"allowJs": false,
"skipLibCheck": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment",
"types": ["vite/client"],
"paths": {
"*": ["./src/@types/*"]
}
},
"include": ["src"]
}

Three.js 導入

1
npm i three @types/three

npm - threeを参考に、以下の通りmain.tsを実装します。(import 周りだけの修正ですね。)

main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import * as THREE from "three/build/three.module";

let camera, scene, renderer;
let geometry, material, mesh;

init();

function init() {
camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.01,
10
);
camera.position.z = 1;

scene = new THREE.Scene();

geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
material = new THREE.MeshNormalMaterial();

mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animation);
document.body.appendChild(renderer.domElement);
}

function animation(time) {
mesh.rotation.x = time / 2000;
mesh.rotation.y = time / 1000;

renderer.render(scene, camera);
}

合わせて、index.htmlのロードするスクリプトをmain.tsを修正します。

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<!-- 読み込み対象を main.ts に変更-->
<script type="module" src="/main.ts"></script>
</body>
</html>

とりあえず、描画できることが確認できます。

TypeScript 対応 2

この後読み込みする .glb.gltf ファイルは、末尾に?urlを付与することで見込みます。
この際このファイルに対しては型定義がないのでエラーが発生します。
型定義を簡単に設定しておきます。

@types\index.d.ts
1
declare module "*?url";

glTF 形式モデル 読み込み 表示

本題の glTF ファイルの読み込みです。
表示されている一がわかりにくいので合わせて軸を表示対象に足しています。

main.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import * as THREE from "three/build/three.module";
// GLTFLoader を使う
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
// ?url を使ってアセットになるファイルのURLを取得する
import modelDataUrl from "./public/model/model.glb?url";

let camera, scene, renderer;
let object, axis, light;

init();

function init() {
camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.set(1, 1, 1);
camera.lookAt(new THREE.Vector3(0, 0, 0));

scene = new THREE.Scene();

light = new THREE.AmbientLight(0xffffff, 1.0);
scene.add(light);

// GLTFLoaderを使って ファイルを読み込む
const gltfLoader = new GLTFLoader();
gltfLoader.load(modelDataUrl, function (data) {
const gltf = data;
console.log(gltf);
object = gltf.scene;
scene.add(object);
});

// 座標情報をはっきりさせるためにx=0 y=0 z=0 に軸表示のヘルパーを置く
axis = THREE.AxisHelper(2000);
axis.position.set(0, 0, 0);
scene.add(axis);

renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animation);
document.body.appendChild(renderer.domElement);
}

function animation(time) {
// オブジェクトが非同期で読み込まれるために
// null ではないことを確認する
if (object) {
object.rotation.x = time / 2000;
object.rotation.y = time / 1000;
}
renderer.render(scene, camera);
}

作成したモデルを描画できることが確認できます。
適当なテクスチャを張ったモデルを作りました。


glTF 形式のいいところはテクスチャを含んだ 1 ファイルにで取り込みが完了するところだと感じます。
マテリアルを別で取り込むのとかの面倒なことがかなり無くなっていました。

久しく触っていなかったライブラリを改めて試すと、発見があるものです。

ではでは。