前回は球体を表示するだけの簡単なアプリでしたが、今回はファイルから3Dイメージをインポートできるようにしましょう。
Three.js with Vue3 Typescript で作る3Dモデル表示アプリハンズオン(導入編)
Three.js with Vue3 Typescript で作る3Dモデル表示アプリハンズオン(ファイルインポート編)[今回]
Three.js with Vue3 Typescript で作る3Dモデル表示アプリハンズオン(投球編)
Three.js with Vue3 Typescript で作る3Dモデル表示アプリハンズオン(Raycaster編)
ゴール
以下のようにファイル入力したものを表示できるようになるのが今回の目標です。(GitHub)
Orbit Controls の追加
背景とグリッドの変更
まずは前回の復習も兼ねて、3D空間の背景とグリッドを変更してみましょう。こんな感じのディレクトリ構成でした。
app/src
├── App.vue
├── assets
│ ├── logo.png
│ └── tailwind.css
├── components
│ └── Three.vue
├── main.ts
└── shims-vue.d.ts
Three.vue
のinit
関数の背景のグリッドの部分を次のように書き換えます。
// 初期化
const init = () => {
if (container.value instanceof HTMLElement) {
...
// 背景のグリッドの追加
scene.add(new GridHelper(50));
scene.background = new Color(0xcccccc);
...
};
Orbit Controls の導入
公式に従って導入していきます。まず、次のようにインポートします。
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
さらに、次のようにインスタンスを生成します。第1引数はCameraオブジェクト、第2引数はRendererのDOMです。
// Three.js
...
const controls = new OrbitControls(camera, renderer.domElement);
あとは、アニメーションフレーム内でコントロールを効かせてやるだけです。以下のように、animate
関数のframe
を変更します。
// 描画
const animate = () => {
const frame = () => {
// カメラの視点変更
controls.update();
// 描画
renderer.render(scene, camera);
// 画面を更新
requestAnimationFrame(frame);
};
frame();
};
ここまでできれば、次のように視点が変更できるようになっているはずです。
ここまでで、以下のようなコードになっているかと思います。
<template>
<div ref="container" class="fixed w-full h-full top-0 left-0"></div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
import {
Color,
GridHelper,
Mesh,
MeshLambertMaterial,
PerspectiveCamera,
PointLight,
Scene,
SphereGeometry,
WebGLRenderer,
} from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
export default defineComponent({
setup() {
// 描画するDOMの指定
const container = ref();
// Three.js
const scene = new Scene();
const camera = new PerspectiveCamera();
const renderer = new WebGLRenderer();
const light = new PointLight();
const controls = new OrbitControls(camera, renderer.domElement);
// 初期化
const init = () => {
if (container.value instanceof HTMLElement) {
// DOMのサイズを取得
const { clientWidth, clientHeight } = container.value;
// 背景のグリッドの追加
scene.add(new GridHelper(50));
scene.background = new Color(0xcccccc);
// ライトの設定
light.color.setHex(0xffffff);
light.position.set(10, 10, 0);
scene.add(light);
// 球体の追加
const sphere = createSphere();
scene.add(sphere);
// カメラの設定
camera.aspect = clientWidth / clientHeight;
camera.updateProjectionMatrix();
camera.position.set(10, 10, 0);
camera.lookAt(0, 0, 0);
// rendererの設定
renderer.setSize(clientWidth, clientHeight);
renderer.setPixelRatio(clientWidth / clientHeight);
container.value.appendChild(renderer.domElement);
// 描画
animate();
}
};
// 描画
const animate = () => {
const frame = () => {
// カメラの視点変更
controls.update();
// 描画
renderer.render(scene, camera);
// 画面を更新
requestAnimationFrame(frame);
};
frame();
};
// マウント時に初期化して描画
onMounted(() => {
init();
});
// Sphereの作成
const createSphere = (): Mesh => {
const geometry = new SphereGeometry(3, 100, 100);
const material = new MeshLambertMaterial();
return new Mesh(geometry, material);
};
return {
container,
};
},
});
</script>
インポート機能の作成
さて、次はいよいよモデルのファイルをインポートして表示していきます。
fbx形式ファイルのインポート
まず、ファイル入力用のHTMLを書きましょう。template
内を以下のように変更します。
<template>
<div ref="container" class="fixed w-full h-full top-0 left-0">
<div
class="
absolute
top-0
right-0
bg-blue-900 bg-opacity-80
text-white
p-5
py-6
"
>
<label
for="file"
class="p-3 cursor-pointer border-2 rounded-lg border-white bg-blue-400"
>ファイルを選択してください</label
>
<input type="file" id="file" class="hidden" @input="onFileInput" />
</div>
</div>
</template>
次にscript部分を書いていきましょう。次のようにローダーをインポートします。
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
さらに、次のようなハンドラーを作ります。内容としては単純にファイル入力をData URLに変換して、ローダーに投げてやるだけです。
// ファイル入力時のハンドラー
const onFileInput = async ({ target }: Event) => {
if (target instanceof HTMLInputElement && target.files) {
// ファイル入力
const file = target.files[0];
// DataURL形式に変換
const dataURL = URL.createObjectURL(file);
// ローダーで読み込み
const loader = new FBXLoader();
const group = await loader.loadAsync(dataURL);
// Sceneに追加
scene.add(group);
}
};
あとはvueの記法に従ってsetup
関数の返り値にハンドラーを追加するだけです。そこまでできたら、以下のようにファイル入力ができるようになります。
そのほかのファイル形式への対応
基本的にはそのほかのファイル形式でも導入方法は同じで、
- ローダーをインポート
- ファイル入力をData URLに変えてローダーに渡す
- ローダーから返されるオブジェクトをSceneに追加する
のようにすればOKです。GitHubの方には、他のファイル形式のものでもロードできるように修正したものをあげておきました。fbx, dae, glb, obj
形式には対応しています。
まとめ
最終的なソースコードは以下のようになりました。
<template>
<div ref="container" class="fixed w-full h-full top-0 left-0">
<div
class="
absolute
top-0
right-0
bg-blue-900 bg-opacity-80
text-white
p-5
py-6
"
>
<label
for="file"
class="p-3 cursor-pointer border-2 rounded-lg border-white bg-blue-400"
>ファイルを選択してください</label
>
<input type="file" id="file" class="hidden" @input="onFileInput" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
import {
Color,
GridHelper,
PerspectiveCamera,
PointLight,
Scene,
WebGLRenderer,
} from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
export default defineComponent({
setup() {
// 描画するDOMの指定
const container = ref();
// Three.js
const scene = new Scene();
const camera = new PerspectiveCamera();
const renderer = new WebGLRenderer();
const light = new PointLight();
const controls = new OrbitControls(camera, renderer.domElement);
// 初期化
const init = () => {
if (container.value instanceof HTMLElement) {
// DOMのサイズを取得
const { clientWidth, clientHeight } = container.value;
// 背景のグリッドの追加
scene.add(new GridHelper(50));
scene.background = new Color(0xcccccc);
// ライトの設定
light.color.setHex(0xffffff);
light.position.set(10, 10, 0);
scene.add(light);
// カメラの設定
camera.aspect = clientWidth / clientHeight;
camera.updateProjectionMatrix();
camera.position.set(10, 10, 0);
camera.lookAt(0, 0, 0);
// rendererの設定
renderer.setSize(clientWidth, clientHeight);
renderer.setPixelRatio(clientWidth / clientHeight);
container.value.appendChild(renderer.domElement);
// 描画
animate();
}
};
// 描画
const animate = () => {
const frame = () => {
// カメラの視点変更
controls.update();
// 描画
renderer.render(scene, camera);
// 画面を更新
requestAnimationFrame(frame);
};
frame();
};
// マウント時に初期化して描画
onMounted(() => {
init();
});
// ファイル入力時のハンドラー
const onFileInput = async ({ target }: Event) => {
if (target instanceof HTMLInputElement && target.files) {
// ファイル入力
const file = target.files[0];
// DataURL形式に変換
const dataURL = URL.createObjectURL(file);
// ローダーで読み込み
const loader = new FBXLoader();
const group = await loader.loadAsync(dataURL);
// Sceneに追加
scene.add(group);
}
};
return {
container,
onFileInput,
};
},
});
</script>
今回はカメラ操作とファイルからモデルを取り込んで表示する機能を追加しました。ここまでできればあとは公式ドキュメント読んでリッチにしていくだけですね!!