React + Three.jsを使って3Dモデルを作ろう①

sanagi
2025-04-08
2025-04-08

こんにちは、sanagiです。

最近、ReactThree.jsを業務で使用することがありました。

Reactは皆さんご存じフロントエンドのフレームワークですが、Three.js3Dコンテンツを扱うためのJavaScriptライブラリのことです。

今回はReact + Three.js を使って簡単な3Dモデルを作ってみたいと思います。

前提

この記事は、以下のような方を対象に書いていきます。

  • Reactの基本概念はなんとなく知っている(componentとかpropsとか)
  • Three.jsに関してはそこまでよく知らない

試してみよう!

今回はこれを作っています↓↓
と言いつつ、申し訳ないですがちょっと読み込み遅いかもしれません(^▽^;)

青い立方体がくるくる回っている画面になったら、読み込み完了です。
停止・再生ボタンも左上についてますので、押してみてください。

今回お試しするにあたって、"StackBlitz"というサービスを利用して動作検証しました。

■StackBlitzとは・・・?(https://stackblitz.com/

  • Webブラウザ上で動作するエディタ(IDE
  • 初期構築(React + TypeScriptVue + JavaScript等)を勝手にやってくれるので、簡単に検証できる。
  • ブログ等で作成したコードを埋め込める
    (※CodePenCodeSandboxとかと同じようなイメージです。)

モジュールのインストール

まず始めに、必要なモジュールのインストールを行いましょう。

npm install three @react-three/fiber @react-three/drei

それぞれを解説すると、以下のような役割のモジュールです。

  • three⇒Three.js(https://threejs.org/)
  • @react-three/fiber⇒Three.js 用のReact レンダラー
  • @react-three/drei⇒@react-three/fiberのヘルパー、いろんな物体を表現できる

ソースコードの解説

インストールも完了して環境も整ったら、さっそく作っていきましょう!

全体の構成

まず、全体の構成に関して解説します。こちら↓完成形の形ですが、

コンポーネントに分解してみると、以下のような構成になっています。


 App(全体の描画)
  ⅬGround(地面)
  ⅬAnimatedBox(BOX)

一つずつ見ていきましょう

①Ground(地面)

/** 地面 */
const Ground = () => {
  return (
    <>
      <mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
        <planeGeometry args={[100, 100]} />
        <meshStandardMaterial color="lightgray" />
      </mesh>
      <gridHelper args={[100, 100]} />
      <axesHelper />
    </>
  );
};

Groundは、名称の通り地面のコンポーネントです。

Groundをさらに分解してみると、以下のように構成されてます。

■各propsは以下のような役割があります。

  • rotation={[-Math.PI / 2, 0, 0]}
     ⇒-Math.PI / 2は、90°をラジアン表記にしたもので、デフォルトは垂直状態なので90°回転させてます。
  • receiveShadow
     ⇒これを入れることで地面に影が表示されるようになります。
  • planeGeometry args={[100, 100]}
     ⇒args={[width, height]で大きさを指定しています。
  • <gridHelper args={[100, 100]} />
    argsに渡している値は[width, height]を入れています。

②AnimatedBox(BOX)

/** AnimatedBoxに渡すPropsの型定義 */
interface AnimatedBoxProps {
  isAnimating: boolean;
}

/** BOX */
const AnimatedBox: React.FC<AnimatedBoxProps> = ({ isAnimating }) => {
  const meshRef = useRef<THREE.Mesh>(null);
  // フレームごとに回転アニメーション
  useFrame(() => {
    if (meshRef.current && isAnimating) {
      meshRef.current.rotation.y += 0.02; // Y軸回転
      meshRef.current.rotation.x += 0.01; // X軸回転
    }
  });
  return (
    <mesh
      ref={meshRef}
      scale={1}
      position={[0, 3, 0]} // 床から少し浮かせる
      castShadow
    >
      <boxGeometry args={[2, 2, 2]} />
      <meshStandardMaterial color="royalblue" />
    </mesh>
  );
};

くるくる回っている青いBOXのコンポーネントです。

レンダーの部分にはBOXのコードのみ書いており、useFrame(@react-three/fiberが提供しているhook)にアニメーションの処理を書くことで、くるくる動いているように見えてます。

useFrameは各フレームに対して毎回実行する内容を書きます。
1フレーム=パラパラ漫画の1枚」という風に考えると分かりやすいです。

つまり、useFrame内のこの処理では、1フレームあたり、XY軸回転をちょっとずつ進めることで、くるくる回転するアニメーションを生成しています。

meshRef.current.rotation.y += 0.02; // Y軸回転
meshRef.current.rotation.x += 0.01; // X軸回転

③App(全体の描画)

const App = () => {
  const [isAnimating, setIsAnimating] = useState(true);
  return (
    <div>
      <Canvas
        shadows
        camera=
        style=
      >
        {/* 環境光 */}
        <ambientLight intensity={0.3} />

        {/* 影を落とす方向光 */}
        <directionalLight castShadow position={[5, 10, 5]} intensity={1.5} />

        {/* 床と影 */}
        <Ground />

        {/* アニメーションするボックス */}
        <AnimatedBox isAnimating={isAnimating} />

        {/* カメラ操作 */}
        <OrbitControls />
      </Canvas>

      {/* ストップボタン */}
      <button
        onClick={() => setIsAnimating((prev) => !prev)}
        style=
      >
        {isAnimating ? '停止' : '再生'}
      </button>
    </div>
  );
};

export default App;

最後は一番親の部分、全体の描画を担う部分です。

基本的に「<Canvas>タグ内にThree.js関連のコンポーネントを入れることで、canvas3Dモデルが描画される」
という仕組みになっています。

<Canvas>内を分解すると以下の構成になっています。

  • 環境光⇒<ambientLight />
  • 方向光⇒<directionalLight />
  • 地面(①のコンポーネント)⇒<Ground />
  • ボックス(②のコンポーネント)⇒<AnimatedBox />
  • カメラ操作⇒<OrbitControls />
  • 再生、停止ボタン⇒<button>

■光源について
Lightの種類は色々あるのですが、ここでは環境光(ambientLight)と方向光(directionalLight)を使ってます。
2つの大きな違いは、影を発生させるか・させないかの違いかなと思います。
「環境光(ambientLight)を強めると影が薄くなり、方向光(directionalLight)を強めると影が強くなる」ような関係性です。
明るさとコントラストを調整したいときに使うと良いかなと思います。

■カメラ操作
<OrbitControls />を置くだけで、ユーザによるカメラ操作ができるようになります。
以下カメラの操作方法です。
・左クリックを押したまま操作⇒カメラがターゲットの周りを周回するような動きをします。
・右クリックを押したまま操作⇒マウスの動きに沿ってカメラが動きます。
・マウスのコロコロを操作⇒拡大・縮小の動きをします。

■再生・停止ボタン
"isAnimating"ステートのtruefalseを切り替える機能を持っています。
1個前に解説した②AnimatedBoxコンポーネントのuseFrame内の処理を見てもらうと分かるのですが、"isAnimating"ステートがtrueにならないと、X・Y軸回転しないようになっていまいます。

以上で、コードの解説は終わりです。

おわりに

3Dって難しそう、、という何となくのイメージがありましたが、意外に簡単に書けるようにできていて、非常に便利なライブラリですね。

もし興味を持った方がいれば、こちらのページに@react-three/fiberを使った作成例がたくさん載っていて面白かったです。
https://r3f.docs.pmnd.rs/getting-started/examples
CodeSandboxでソースコードも公開されているので、是非見てみてください。

それではまた次回!