[Astro #41] React Three FiberでGLBモデルのステージ配置と動的切り替えを実装

[Astro #41] React Three FiberでGLBモデルのステージ配置と動的切り替えを実装

はじめに

前回の記事 「Astro #40: React Three Fiberのローディング画面とProgress Dialogの落とし穴」に引き続き、今回は「PROTOCOL.LAIN」の世界観をさらに強化するため、GLBモデルを用いた「ステージ(舞台装置)」の配置と、メニューからの動的切り替え機能を実装した。

後で読み返して何をやったか思い出せるよう、今回の実装内容をざっと備忘録メモで残します。

前回の記事:

Youtube:

動画:

スクショ:

[Astro #41] React Three FiberでGLBモデルのステージ配置と動的切り替えを実装 [Astro #41] React Three FiberでGLBモデルのステージ配置と動的切り替えを実装 [Astro #41] React Three FiberでGLBモデルのステージ配置と動的切り替えを実装 [Astro #41] React Three FiberでGLBモデルのステージ配置と動的切り替えを実装

実装の概要

今回のアップデートで、アバターの背後に巨大な歯車やサイバーなポータルなどのステージを配置し、さらにメニューから自由に切り替えられるようにした。

1. ステージモデルの読み込みと自転アニメーション

@react-three/dreiuseGLTF を使ってGLBモデルを読み込む WiredStage コンポーネントを作成しました。 また、単なる静的な背景ではなく「生きているシステム」感を出すため、useFrame を使い、モデル自体の重心を軸にしてゆっくりと自転(Y軸回転)するアニメーションを加えています。

// WiredStageコンポーネントの要点
const { scene } = useGLTF(data.path);
const primitiveRef = useRef<THREE.Group>(null!);

useFrame((state, delta) => {
  if (primitiveRef.current) {
    // 経過時間(delta)を使ってフレームレートに依存しない一定速度で自転させる
    primitiveRef.current.rotation.y += delta * data.rotationSpeed;
  }
});

return (
  <group position={data.position} scale={data.scale}>
    {/* Centerを使ってモデルの個体差(ズレ)を吸収 */}
    <Center>
      <primitive object={scene} ref={primitiveRef} receiveShadow />
    </Center>
  </group>
);

※親の <group> でシーン内の位置を決め、子である <primitive> に ref を付けて回すことで、変な旋回をせずに綺麗に自転させることができます。

2. JSONによるステージ管理と動的切り替え

モデルごとに最適な座標(position)、スケール(scale)、回転速度(rotationSpeed)が異なるため、これらを stages.json にまとめて一元管理するようにしました。 これにより、コンポーネント側のコードを汚すことなく、JSONに追記するだけで新しいステージを簡単に追加できるようになりました。NONE(非表示)状態もこのJSON内で管理しています。

3. メニューUIの拡張とスマホ対応(スクロール)

アバターの調整メニュー(WiredAvatarMenu)に、ステージ選択用のサムネイル付きリスト(ENVIRONMENT_SETTINGS)を追加しました。

ここで問題になったのが、項目が増えたことでメニューがスマホの画面(ビューポート)からはみ出してしまい、CLOSEボタンが押せなくなるというUIのバグです。 これを解決するため、メニューのインラインスタイル(JS側)で maxHeight: ‘80vh’ と overflowY: ‘auto’ を設定しました。さらにグローバルCSSのメディアクエリと組み合わせて、画面内に収めつつ内部スクロールができるように改修しています。 合わせてスクロールバーもサイバーなネオンカラーにカスタマイズし、世界観の統一を図りました。

4. ステージモデルの読み込みと自転アニメーション

(中略:先ほどのコードを記載)

【技術的Tips】座標の累積ズレ(ドリフト)の回避

実装中、一度選択したステージを再度選択すると、モデルの表示位置が少しずつズレていくという問題に直面しました。これは <Center> コンポーネントが読み込まれた scene の座標データを直接書き換えてしまうことが原因です。

これを防ぐため、読み込んだ scene をそのまま使うのではなく、clone() を用いてインスタンスを複製するようにしました。また、毎フレームのクローンによる負荷を避けるため、useMemo でパス変更時のみ実行されるよう最適化しています。

// 座標ドリフトを回避する実装
const { scene } = useGLTF(data.path);
const clonedScene = React.useMemo(() => scene.clone(), [scene]);

return (
  <group key={data.path} position={data.position} scale={data.scale}>
    <Center>
      <primitive object={clonedScene} ref={primitiveRef} />
    </Center>
  </group>
);

5. 3Dモデルの制作者様とアセットの購入について

今回、圧倒的な「舞台装置」の構築にあたり、「Official Satori」 様、「おしり服屋さん」「minipper」様 の3Dモデル/衣装を使用させていただきました。

Blenderを完全に使いこなした緻密なスチームパンク構造や、サイバーで抽象的な空間の造形美に惚れ込み、アセットを購入してます。

一からこのレベルのハードサーフェスモデルを作ったり依頼したりすれば莫大なコストと時間がかかりますが、ワンコインでこのクオリティの世界を自分のプロジェクトに組み込めるのは本当にありがたいです。

深い敬意を表し、画面右下のフッターにシステムログと馴染む形でクレジット表記を追加しています。

まとめ

ステージ(環境)が加わったことで、単なる「3Dアバターのビューアー」から「一つの完結した世界(Wired)」へと一気に進化しました。 Environment によるライティングの反射や、手前のアバターと奥で回る巨大な構造物のコントラストが絶妙で、これまでの制作物の中でも最高傑作と呼べるレベルの空間が出来上がったと思います。(MMD時計はお蔵入りでいいかもしれません)

BlenderやVRoid Studioを自分でも使いこなせるようになれば表現の次元がさらに変わるので、今後はこの「舞台」をベースに、演出面をさらにブラッシュアップしていきたいです。