Three.jsをVue3のコンポーネントに〜Blenderで作成したオブジェクトを読み込む〜

こんにちは｜こんばんは。カエルのアイコンで活動しております @kyamaz :frog: です。

## はじめに
本稿は、@0taUtanさん、@enumura1さんや@nannany_storesさんの次の寄稿を参考にThree.jsをVue3のコンポーネントにして、Blenderで作成したオブジェクトを読み込む手順を取り上げます。

https://qiita.com/0taUtan/items/c4ded63cd2c912b4ce74

https://qiita.com/0taUtan/items/e71436beaf612e7bb93f

https://qiita.com/enumura1/items/c62f15c4fbeb541fa830

https://qiita.com/nannany_stores/items/c92d9f05588c751077b1

## Blenderでモデルを作成する
３Dモデルのサンプルとして、@PlusFさんの『[BlenderでカーボンナノチューブのCGをつくる](https://qiita.com/PlusF/items/783743d093b01ae73dfe)』を参考にカーボンナノチューブ（CNT）のモデルを作成します。

![Blenderの画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/0500b179-e585-5aa6-c078-37a2faa39e4d.png)

Blenderのメニューから`File → Export → glTF 2.0 (.glb/gltf)`を選択して、任意の場所に glTFファイル へエクスポートします。

![Blenderエクスポート](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/6dba8ffd-b8e3-2f75-4574-de582445a843.png)

ファイル名を"CNT.glb"とします。

## Vue 3 + TypeScript + Vite の環境を整える
拙稿『p5.jsをVue3のコンポーネントにする』と[同じ手順](https://qiita.com/kyamaz/items/2a77685b091065050eea#vue-3--typescript--vite-%E3%81%AE%E7%92%B0%E5%A2%83%E3%82%92%E6%95%B4%E3%81%88%E3%82%8B)で環境を整えます。そして`Home.vue`を、次のようにコンポーネント`HelloWorld`を使うように変更します。
```vuejs: Home.vue
<script lang="ts">
import HelloWorld from './components/HelloWorld.vue'
export default {
  components: {
    HelloWorld
  }
}
</script>

<template>
  <div>
    <HelloWorld />
  </div>
</template>

<style scoped>
</style>
```

## Three.jsをコンポーネントとして活用する
Three.jsのモジュールを使用できるようにインストールします。同時にTypeScriptで型チェックができるように、開発のためのモジュール@types/threeもインストールします。
```shell-session:
% npm install three
% npm install -D @types/three
```
Three.jsをコンポーネントにするには次のように書きます。
<details><summary>HelloWorld.vueのソースコード全体</summary>

```vuejs:HelloWorld.vue
<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.js";
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

export default defineComponent({
  name: "HelloWorld",
  setup() {
    // 描画するDOMの指定
    const container = ref();
    // Three.js
    const scene = new Scene();
    const camera = new PerspectiveCamera();
    const renderer = new WebGLRenderer();
    const light = new PointLight();
    const loader = new GLTFLoader();
    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);
        // オブジェクトの追加
        loader.load("assets/cnt.glb", (gltf: GLTF) => {
          const model = gltf.scene;
          model.scale.set(0.25, 0.25, 0.25);
          scene.add(model);
        });
        // カメラの設定
        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>

<template>
  <div ref="container" class="fixed w-full h-full top-0 left-0"></div>
</template>

<style scoped>
.fixed { position: fixed; }
.w-full { width: 100%; }
.h-full { height: 100%; }
.top-0 { top: 0px; }
.left-0 { left: 0px; }
</style>
```
</details>

```vuejs:HelloWorld.vue（抜粋）
<script lang="ts">
import { defineComponent, onMounted, ref } from "vue";
import {
  Color,
  GridHelper,
  PerspectiveCamera,
  PointLight,
  Scene,
  WebGLRenderer
} from "three";

export default defineComponent({
  name: "HelloWorld",
  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;
        // :
        // :
        // 描画
        animate();
      }
    };
    // 描画
    const animate = () => {
      const frame = () => {
        // 描画
        renderer.render(scene, camera);
        // 画面を更新
        requestAnimationFrame(frame);
      };
      frame();
    };
    // マウント時に初期化して描画
    onMounted(() => {
      init();
    });
    return {
      container,
    };
  },
});    
</script>

```

### Three.js にて glTFファイル をロードする
上記で作成した"CNT.glb"をプロジェクトフォルダの`public/assets/`に移動しておきます。そして、HelloWorld.vue に次のように`GLTFLoader`を読み込むためのimport文を記載をします。

```typescript:
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
```
さらに、オブジェクトを追加するところにファイルをロードする処理を記述します。

```typescript:
        // オブジェクトの追加
        loader.load("assets/cnt.glb", (gltf: GLTF) => {
          const model = gltf.scene;
          model.scale.set(0.25, 0.25, 0.25);
          scene.add(model);
        });
```

これを`npm run dev`で試してみると、次のようにモデルがブラウザで表示されます。

![イメージ１](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/aabbd171-3a6b-ea81-063f-e4c0d6630eec.png)

![イメージ２](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136950/5c73314d-113c-84d0-d3c4-b167cd1387ba.png)

`npm run build`でビルドする際には、`OrbitControls`のローカル変数のところでエラーになるため、`tsconfig.json`のパラメータを`"noUnusedLocals": false`に書き換えておく必要があります。

## おわりに
本稿では、いくつかの記事を参考にして、Three.jsをVue3で扱えるようにした環境で、Blenderで作成したカーボンナノチューブのモデルをブラウザに表示する手順を取り上げました。

<details><summary>本稿の環境</summary>

本稿のために使用した環境は以下となります。
macOS: Sonoma 14.4 (chip: Apple M1)
Blender: 4.0.2
anyenv: 1.1.5（homebrewにてインストール）
node: 18.19.0（anyenvのnodenvでインストール）
npm: 10.5.0
@vue/cli 5.0.8
three: 0.162.0
vue: 3.4.21
vue-router: 4.3.0
typescript:5.2.2
vite: 5.2.0
</details>

（●）（●） Happy Hacking!
/""　＿＿""＼