【Babylon.js】Reactを使って3Dモデルを表示する

こんな感じでサッカーボールを表示してみました。

https://codesandbox.io/embed/smoosh-silence-rj26ot?fontsize=14&hidenavigation=1&theme=dark&view=preview

# はじめに

ARIの土井です。前回[【Babylon.js】Playgroundで遊んでみよう](https://qiita.com/yuki_doi/items/ac201cac8220d613d819)という記事を書きました。今回はReactを使って実際にWebページに3Dモデルを表示してみたいと思います。
※ Reactの実行環境は、Web上で利用できる統合開発環境の[CodeSandbox](https://codesandbox.io/s/)を使用しました。

# 準備

まず、https://codesandbox.io/s/ にアクセスして`Satrt from a template`から`React`をクリックします。
![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507339/86be37e0-7a49-ad72-9127-337fff936257.png)

以下のようにReactのテンプレートから生成されたサンプルページが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507339/c234c12d-4642-5c21-8e74-8c40b7658735.png)

`Dependencies`から`@babylonjs/core`パッケージをインストールしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507339/de69f21e-1979-6ece-e412-c519d460fdf3.png)

# 手順

ここからReactのテンプレートで生成されたディレクトリを以下のような構成にしていきます。

```
public/
　└ index.html
src/
　├ assets/
　│  └ soccerBall.babylon
　├ components/
　│  └ SceneComponent.js
　├ App.js
　├ index.js
　└ styles.css
package.json
```

## SceneComponent.jsの作成

[Babylon.jsの公式ドキュメント](https://doc.babylonjs.com/communityExtensions/Babylon.js+ExternalLibraries/BabylonJS_and_ReactJS#component)の通り、`SceneComponent.js`を新規で作成します。`src`フォルダの配下に`components`フォルダを新規作成して、`SceneComponent.js`を作成します。

```react:SceneComponent.js
import { useEffect, useRef } from "react";
import { Engine, Scene } from "@babylonjs/core";

export default ({
  antialias,
  engineOptions,
  adaptToDeviceRatio,
  sceneOptions,
  onRender,
  onSceneReady,
  ...rest
}) => {
  const reactCanvas = useRef(null);

  // set up basic engine and scene
  useEffect(() => {
    const { current: canvas } = reactCanvas;

    if (!canvas) return;

    const engine = new Engine(
      canvas,
      antialias,
      engineOptions,
      adaptToDeviceRatio
    );
    const scene = new Scene(engine, sceneOptions);
    if (scene.isReady()) {
      onSceneReady(scene);
    } else {
      scene.onReadyObservable.addOnce((scene) => onSceneReady(scene));
    }

    engine.runRenderLoop(() => {
      if (typeof onRender === "function") onRender(scene);
      scene.render();
    });

    const resize = () => {
      scene.getEngine().resize();
    };

    if (window) {
      window.addEventListener("resize", resize);
    }

    return () => {
      scene.getEngine().dispose();

      if (window) {
        window.removeEventListener("resize", resize);
      }
    };
  }, [
    antialias,
    engineOptions,
    adaptToDeviceRatio,
    sceneOptions,
    onRender,
    onSceneReady
  ]);

  return <canvas ref={reactCanvas} {...rest} />;
};
```

## .babylonファイルの作成

[前回の記事](https://qiita.com/yuki_doi/items/ac201cac8220d613d819#3d%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AE%E7%94%A8%E6%84%8F)で作成した`.glb`ファイルを、`.babylon`ファイルに変換します。

:::note
CodeSandboxに匿名投稿する際はファイルのアップロードができません。そのため今回はバイナリファイルである`.glb`を、`.babylon`というJSON形式に変換し、JSONの文字列をコピペでCodeSandBoxに反映するという方法をとりました。
:::

`.glb`形式のファイル`.babylon`に変換するには、Babylon.jsが提供してくれている[Babylon.js Sandbox](https://sandbox.babylonjs.com/)を使用します。

まず、[Babylon.js Sandbox](https://sandbox.babylonjs.com/)にアクセスして、`.glb`ファイルをアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507339/e3bad807-d3b8-7857-df68-ed2a9cd07c55.png)

次に、サッカーボールのノード名をプログラムからアクセスしやすくするために変更しておきます。`Properties` > `Name` で `SoccerBall`と入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507339/0828d3fe-d475-5bd4-2b7f-f3ee9a22b454.png)

`Tools` > `SCENE EXPORT` > `Export to Babylon`をクリックすると、`scene.babylon`という名前でダウンロードできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507339/6791ef60-ed78-00a0-448d-4dd8c0f6b9ff.png)

ダウンロードしたファイル内のJSON文字列を`src/assets/SoccerBall.babylon`にコピペで貼り付けます。

## App.jsの修正

Reactのテンプレートから生成された`App.js`を以下に置き換えます。

```react:App.js
import * as BABYLON from "@babylonjs/core";
import SceneComponent from "./components/SceneComponent";
import SoccerBallAsset from "./assets/SoccerBall.babylon";

import "./styles.css";

const onSceneReady = (scene) => {
  // カメラの作成, 回転
  const camera = new BABYLON.ArcRotateCamera("camera", -2.5, 2, 1.5, new BABYLON.Vector3(0, 0, 0), scene);
  camera.useAutoRotationBehavior = true;
  camera.autoRotationBehavior.idleRotationSpeed *= 5;
  camera.attachControl(SceneComponent, true);

  // ライトの作成
  const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
  light.intensity = 1.0;

  // SoccerBall.babylonからノードの読み込み
  const assetsManager = new BABYLON.AssetsManager(scene);
  assetsManager.addMeshTask("task", "SoccerBall", SoccerBallAsset); //scene.babylonのノード名SoccerBallを追加
  assetsManager.load();
};

export default function App() {
  return (
    <SceneComponent antialias onSceneReady={onSceneReady} id="my-canvas" />
  );
}
```

`SceneComponent`のプロパティとして渡す`onSceneReady`関数を作成します。この関数内でカメラやライト、3Dモデルなどを定義していきます。基本的に`import * as BABYLON from "@babylonjs/core";`とすれば、あとは[Playground](https://playground.babylonjs.com/)の`createScene`関数の内容を記載していけば良いのですが以下の点に注意してください。

* Playgroundで定義している`var scene = new BABYLON.Scene(engine);`は、`SceneComponent.js`側で定義しているので不要
* Playgroundで参照している`canvas`要素は、`SceneComponent`に置き換える
    * 上記のコードでいうと、`camera.attachControl(SceneComponent, true);`の部分になります。

# まとめ

今回はReactを使ってBabylon.jsの3Dモデルを表示してみました。これからもBabylon.js関連の記事を書いていこうと思います。