Blenderの破壊シミュレーションをエクスポートしてthree.jsで利用する

Blenderの破壊シミュレーションをエクスポートして、three.jsで利用する方法について解説します。

流れとしては次の通りです。

1. Blenderで破壊シミュレーションを作成する
1. 破壊シミュレーションをキーフレームにベイクしてglTFでエクスポートする
1. glTFをthree.jsで読み込みアニメーションを再生する

検証に利用した各ツールのバージョンは次のとおりです。

- **Blender:** 3.2.0
- **three.js:** r150

## 1. Blenderで破壊シミュレーションを作成する


まず、`Edit > Preferences > Add-ons`からCell Fractureアドオンを有効化します。

破壊するオブジェクトを選択した状態で`Object > Quick Effects > Cell Fracture`からダイアログを表示します。ここで設定をしてから`OK`を押すと、選択したオブジェクトが設定に基づき分割されます(元の破壊前のオブジェクトも残ります)。
![CellFractureDialog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/6054145b-7fd5-5ef0-0387-ebbd18b3dc63.png)
作成された破片をすべて選択した状態で`Object > Rigid Body > Add Active`を選択し、破片が剛体シミュレーションに従うようにします。
地面にぶつけたいので、適当な大きさの平面を作成し、選択した状態で`Object > Rigid Body > Add Passive`を選択することで障害物として機能するようにします。

これでタイムラインを再生すると、地面にぶつかって破片がばらばらになる様子が確認できます。
![Blender.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/33ebc757-11c2-0bd2-e620-af2c17503146.gif)

これで簡単ですが、Blender上での破壊シミュレーションの完成です。Cell Fractureを使った破壊シミュレーションはより詳細な記事が検索すれば多々見つかるのでそちらを参考にするのがいいと思います。

## 2. 破壊シミュレーションをキーフレームにベイクしてglTFでエクスポートする

すべての破片を選択した状態で`Object > Rigid body > Bake to Keyframes`を選択します。ベイクするキーフレームの間隔を設定するダイアログがでるので適当に設定して`OK`を押すと剛体シミュレーションのアニメーションをキーフレームにベイクできます。
すべての破片を選択して、`File > Export > glTF 2.0 (.gltf/.glb)`から破片のメッシュとキーフレームアニメーションをglTFでエクスポートします。エクスポートのダイアログで`Include > Limit to > Selected Objects`を有効にして、選択しているオブジェクトだけをエクスポートするようにします。

## 3. glTFをthree.jsで読み込みアニメーションを再生する

エクスポートしたglTFをthree.jsで読み込み、アニメーションを再生します。メッシュとアニメーションが別々に読み込まれるので、読み込み後にそれぞれのメッシュとアニメーションを紐づける必要があります。

```html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Blender Destruction to Three.js</title>
    <style>
      body {
        margin: 0;
      }
    </style>
  </head>
  <body>
    <script type="importmap">
      {
        "imports": {
          "three": "./three.module.js",
          "three/addons/": "./examples/jsm/"
        }
      }
    </script>
    <script type="module">

      import * as THREE from 'three';
      import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
      const scene = new THREE.Scene();
      const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
      camera.position.set(5, 5, 5);
      camera.lookAt(new THREE.Vector3(0, 0, 0));
      const renderer = new THREE.WebGLRenderer();
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);

      const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
      dirLight.position.set(2, 5, 2);
      scene.add(dirLight);
      const light = new THREE.AmbientLight(0x333333);
      scene.add( light );

      const mixers = [];
      const loader = new GLTFLoader();
      loader.load(
        './destruction.glb',
        (gltf) => {
          scene.add(gltf.scene);
          // メッシュとアニメーションを紐づける
          gltf.scene.children.forEach((child, index) => {
              const mixer = new THREE.AnimationMixer(child);
              mixers.push(mixer);
              const action = mixer.clipAction(gltf.animations[index]);
              action.play();
          });
        }
      );

      const clock = new THREE.Clock(true);
      const update = () => {
        requestAnimationFrame(update);

        const delta = clock.getDelta();
        mixers.forEach(mixer => mixer.update(delta));

        renderer.render(scene, camera);
      }
      update();

      const onResize = () => {
        const width = window.innerWidth;
        const height = window.innerHeight;
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(width, height);
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
      }
      window.addEventListener('resize', onResize);
    </script>
  </body>
</html>
```

ブラウザで確認すると次のようになり、Blenderの破壊シミュレーションをエクスポートしてthree.jsで利用できることが確認できました。
![Threejs.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61569/e30d75ca-e11f-1e67-9a53-587c6524be69.gif)