今回からThree.jsを使って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)
導入
リポジトリの作成
とりあえずまずはvue projectの作成です。(vue-cliが入っていない人は公式を参考にnpmインストールしてください。)とりあえずTypeScriptを指定してプロジェクトを作成します。(vueのセットアップが面倒な人はThree.jsの導入までスキップしてください。レポジトリをクローンすればinit
ブランチからすぐに始められます。)
vue create app
cd app
ここからはコードを書き換えていきましょう。
まず、Helloworld.vue
をThree.vue
という名前に書き換えます。(便宜上)
次に、App.vue
のApp.vue
内のアイコンを削除し、Three.vue
をインポートします。
<template>
<Three />
</template>
<script lang="ts">
import { defineComponent } from "vue";
import Three from "./components/Three.vue";
export default defineComponent({
name: "App",
components: {
Three,
},
});
</script>
最後にThree.vue
を次のように書き換えます。なお、cssのスタイリングはtailwindcssで行なっていきます。(適宜vue add tailwind
してください。)
<template>
<div></div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({});
</script>
Three.jsの導入
まず、Three.jsをnpmインストールします。TypeScriptなので@types/three
もお忘れなく。
npm install -D three @types/three
次に、Three.vue
を次のように書き換えます。各要素の解説は後ほど。
<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 { GridHelper, PerspectiveCamera, Scene, WebGLRenderer } from "three";
export default defineComponent({
setup() {
// 描画するDOMの指定
const container = ref();
// Three.js
const scene = new Scene();
const camera = new PerspectiveCamera();
const renderer = new WebGLRenderer();
// 初期化
const init = () => {
if (container.value instanceof HTMLElement) {
// DOMのサイズを取得
const { clientWidth, clientHeight } = container.value;
// 背景のグリッドの追加
scene.add(new GridHelper());
// カメラの設定
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 = () => {
// 描画
renderer.render(scene, camera);
// 画面を更新
requestAnimationFrame(frame);
};
frame();
};
// マウント時に初期化して描画
onMounted(() => {
init();
});
return {
container,
};
},
});
</script>
ここで、おそらくTSのエラーが出るのでtsconfig.tsのコンパイラオプションに"ES2020"
を追記します。
{
"compilerOptions": {
...
"lib": [
"ES2020",
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
...
}
ここまでできたら、npm run serve
で起動し、localhost:8080
にアクセスしてみましょう。以下のような感じでグリッドが表示されているだけの真っ暗な画面が表示されればOKです。
解説
Three.vue
内のTSコードを見ていきましょう。まずは変数の定義から。
// Three.js
const scene = new Scene();
const camera = new PerspectiveCamera();
const renderer = new WebGLRenderer();
Blenderなどの3Dモデルソフトを使ったことがある方なら馴染み深いかもしれません。これらはそれぞれ次のようなものです。(詳しくは公式を参照)
-
Scene
: 描画する3D空間のオブジェクトが入るクラス -
Camera
: 3D空間のビューポイント -
Renderer
: 3D空間をcanvasに描画するためのクラス
次に初期化を見ていきましょう。
// 初期化
const init = () => {
if (container.value instanceof HTMLElement) {
// DOMのサイズを取得
const { clientWidth, clientHeight } = container.value;
// 背景のグリッドの追加
scene.add(new GridHelper());
// カメラの設定
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();
}
};
コメントに書いてある通りですが、ここで行なったのは以下の3つです。
- シーンにグリッドを追加
- カメラのアスペクト比・位置・視線の変更
- 描画のサイズ・アスペクト比調整とDOMへのアペンド
最後に、animate
関数でアニメーションとして描画を行なっています。animate
関数を見てみましょう。
// 描画
const animate = () => {
const frame = () => {
// 描画
renderer.render(scene, camera);
// 画面を更新
requestAnimationFrame(frame);
};
frame();
};
これもコメントに書いてある通りですが、アニメーションの各フレームで描画を行なっています。
オブジェクトの挿入と視点の変更
球体の追加
このままでは何もないので、空間に球体を追加しましょう。次のような関数を作成します。
// Sphereの作成
const createSphere = (): Mesh => {
const geometry = new SphereGeometry(3);
const material = new MeshBasicMaterial({ color: 0xffffff });
return new Mesh(geometry, material);
};
基本的なオブジェクトの作成方法は、作りたいオブジェクトの形状であるGeometryとその色などの性質を決めるMaterialを作成し、そこからMeshというオブジェクトを作成するという手順です。ここでは、半径3で白色の球体を作成しました。
あとは、次のようにしてシーンに追加します。
const init = () => {
if (container.value instanceof HTMLElement) {
...
// 球体の追加
const sphere = createSphere();
scene.add(sphere);
...
}
};
このままだと球体に見えないので、Materialを変更して、光源を追加して影をつけてみましょう。
const light = new PointLight();
// 初期化
const init = () => {
if (container.value instanceof HTMLElement) {
...
// ライトの設定
light.color.setHex(0xffffff);
light.position.set(10, 10, 0);
scene.add(light);
...
}
};
...
// Sphereの作成
const createSphere = (): Mesh => {
const geometry = new SphereGeometry(3);
const material = new MeshLambertMaterial();
return new Mesh(geometry, material);
};
今回はLamberMaterialにしてみました。次のように見えるはずです。
カメラの視点変更
最後に、カメラの視点を変更してみます。
// 描画
const animate = () => {
// カメラの位置パラメータ
let phi = 0;
const frame = () => {
...
// カメラの視点変更
phi += 0.003 * Math.PI;
camera.position.set(10 * Math.cos(phi), 10, 10 * Math.sin(phi));
camera.lookAt(0, 0, 0);
...
};
frame();
};
若干数学チックですが、こんな感じでアニメーションフレームを修正します。単純にx,z座標を極座標を使って回転させるだけですね。0.003
のレートを変えれば回転スピードが制御できます。
ここまでできればゴールです!冒頭で見せたGIFのようになれば完了です。
#まとめ
最終的なソースコードは以下のようになりました。
<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 {
GridHelper,
Mesh,
MeshLambertMaterial,
PerspectiveCamera,
PointLight,
Scene,
SphereGeometry,
WebGLRenderer,
} from "three";
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 init = () => {
if (container.value instanceof HTMLElement) {
// DOMのサイズを取得
const { clientWidth, clientHeight } = container.value;
// 背景のグリッドの追加
scene.add(new GridHelper());
// ライトの設定
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 = () => {
// カメラの位置パラメータ
let phi = 0;
const frame = () => {
// 描画
renderer.render(scene, camera);
// カメラの視点変更
phi += 0.002 * Math.PI;
camera.position.set(10 * Math.cos(phi), 10, 10 * Math.sin(phi));
camera.lookAt(0, 0, 0);
// 画面を更新
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>
今回は、単純にオブジェクトを作成して視点を回転しながら表示させるだけでした。次回は実際にオブジェクトをファイルから取り込んでみようと思います。