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

sanagi
2025-06-03
2025-06-03

こんにちは、sanagiです。

前回は、react-threeを使って3Dモデル作って遊んでみたのですが(前回の記事はこちら⇒https://blog.future.ad.jp/react-three1)、

今回は続きとして、react-threeの違う機能をお試ししてみたいと思います。

今回使うのはこれ↓↓
@react-three/rapier

3Dモデルで物理演算を扱うためのライブラリで、物体を落としたり弾ませたり等、実際の物体の動きを再現するような処理ができるようになります。

早速やってみましょう!

前提

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

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

※前回の記事で書いた部分は一部端折っていたりするので、もし分からない部分あったら前回の記事に書いてあるかもしれません。(前回の記事はこちら⇒https://blog.future.ad.jp/react-three1

やってみよう

まず、完成形を載せます。
申し訳ないですが、ちょっと読み込み遅いかと思います。1分くらい、、

赤いBOXが格子状の地面に落ちた状態であれば読み込み完了です。
「力を加える」ボタンを押すとBOXが上に投げられるようになっていますので押してみて下さい!

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

まずは、必要なモジュールをインストールします。

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

@react-three/rapier以外は前回も使ったモジュールです。

  • three⇒Three.js
  • @react-three/fiber⇒Three.js 用のReact レンダラー
  • @react-three/drei⇒@react-three/fiberのヘルパー、いろんな物体を表現できる
  • @react-three/rapier⇒@react-three/fiberで物理演算を扱うためのライブラリ

ソースコードの解説

先ほど載せたStackBiltzのソース通りです!となるとブログ終わっちゃうので、ソースコードについて解説していきます。

全体の構成

まずは全体のコンポーネント構成ですが、以下のようになります↓↓


 App(全体の描画)
  Ⅼ Ground(地面)
  Ⅼ BoxObj(BOXオブジェクト)
  Ⅼ ThrowBoxBtn(BOXを投げるボタン)

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

①Ground(地面)

/** 地面 */
const Ground = () => {
  return (
    <RigidBody type="fixed" restitution={1}>
      <mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial color="#ffffc2" />
      </mesh>
      <gridHelper args={[10, 10]} />
      <axesHelper />
    </RigidBody>
  );
};

Groundコンポーネントは、前回の記事で作ったものをほぼ流用していますので、メインは前回の記事を見て頂くと良いです。

ここで特に解説したいのが、<RigidBody>タグです。

@react-three/rapierの基本的な使い方として、以下のように<Physics>、<RigidBody>で挟むことによって、物理要素を加えることができます。
Groundコンポーネントも<RigidBody>で挟み込むことによって物理要素を付与しています。

<Physics>
    <RigidBody>
        <物理要素を加えたいコンポーネント①>
    </RigidBody>
    <RigidBody>
        <物理要素を加えたいコンポーネント②>
    </RigidBody>
</Physics>

RigidBodyコンポーネントの各props解説

  • type="fixed"
    「固定、動かない」オブジェクトであることを指定しています。
    ここでは動かない地面なので、"fixed"を指定していますね
  • restitution={1}
    弾力の強さを指定してます。
    数字を大きくするほど物体が地面に落ちたときの弾みが大きくなります。
    BOXが落ちたときに弾むのはこれの影響です。

②BoxObj(BOXオブジェクト)

/** BoxObjのprops型 */
interface BoxObjProps {
  boxRef: React.RefObject<RapierRigidBody | null>;
}

/** BOX */
const BoxObj: React.FC<BoxObjProps> = ({ boxRef }) => {
  return (
    <RigidBody ref={boxRef} type="dynamic" position={[0, 5, 0]}>
      <mesh castShadow>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color="red" />
      </mesh>
    </RigidBody>
  );
};

こちらもGroundコンポーネントと同様に、<RigidBody>コンポーネントに入れることで物理要素を与えています。

RigidBodyコンポーネントの各props解説

  • ref={boxRef}
    useRef でオブジェクトを参照し、外部から操作できるようにします。
    後に解説しますが、「力を加える」ボタンでBoxオブジェクトを操作するために入れています。
  • type="dynamic"
    動的(dynamic)な物理オブジェクト で、重力や衝突の影響を受けるように設定しています。
  • position={[0, 5, 0]}
    初期位置(x, y, z)を指定しています。

③ThrowBoxBtn(BOXを投げるボタン)

/** ThrowBoxBtnのprops型 */
interface ThrowBoxBtnProps {
  boxRef: React.RefObject<RapierRigidBody | null>;
}

/** BOXを投げるボタン */

const ThrowBoxBtn: React.FC<ThrowBoxBtnProps> = ({ boxRef }) => {
  // ボックスを投げる関数
  const throwBox = () => {
    if (boxRef.current) {
      // ボックスに力を加える(Y軸に10の強さで力を加える)
      boxRef.current.applyImpulse({ x: 0, y: 10, z: 0 }, true);
    }
  };
  return (
    <button
      onClick={throwBox}
      style=
    >
      力を加える
    </button>
  );
};

画面上でいう「力を加える」ボタンのコンポーネントですね。

ここのコンポーネントでは、こんなことをやっています。
 ①propsで渡ってきたBOXのrefに力を加える関数(throwBox関数)を定義
 ②①の関数をonClick時に走らせる

■throwBox関数について解説
applyImpulse() は 瞬間的な力 を加える関数です。
各引数で以下の設定をしています。
 ・{ x: 0, y: 10, z: 0 } ⇒Y軸に10の強さで力を加える
 ・true ⇒ボディの重心に対して力を加える

④App(全体の描画)

const App = () => {
  const boxRef = useRef<RapierRigidBody>(null);
  return (
    <div>
      <Canvas
        shadows
        camera=
        style=
      >
        {/* 物体が落ちるコンポーネント */}
        <Physics gravity={[0, -9.81, 0]}>
          {/* 地面 (固定) */}
          <Ground />
          {/* 落ちる物体 */}
          <BoxObj boxRef={boxRef} />
        </Physics>
        {/* 環境光 */}
        <ambientLight intensity={0.3} />
        {/* 影を落とす方向光 */}
        <directionalLight castShadow position={[5, 10, 5]} intensity={1.5} />
        {/* カメラを動かせるようにする */}
        <OrbitControls />
      </Canvas>
     {/* BOXを投げるボタン */}
      <ThrowBoxBtn boxRef={boxRef} />
    </div>
  );
};
export default App;

最後に親である、全体の描画を行うコンポーネントです。

以下のような構成です。

  • 物理要素コンポーネント⇒<Physics />
    ・地面(①のコンポーネント)⇒<Ground />
    ボックス(②のコンポーネント)⇒<BoxObj />
  • 環境光⇒<ambientLight />
  • 方向光⇒<directionalLight />
  • カメラ操作⇒<OrbitControls />
  • BOX投げるボタン(③のコンポーネント)⇒<ThrowBoxBtn />

光源、カメラ関係は前回記事のものをパクっているので、真新しい部分は少ないのですが、特筆したいのが、<Physics gravity={[0, -9.81, 0]}>の部分です。

この"gravity"propsはx, y, z方向の重力ベクトルを表していて、ここでは、gravity={[0, -9.81, 0]}と指定しているのでY軸の下向きに9.81m/s²(地球の重力)がかかる環境であることを表現しています。

おわりに

解説は以上です。最後まで読んで頂きありがとうございました。

ここまで来たら、簡単な3Dゲームなら作れそうな気になってきましたね(笑)

前回の記事にも書いたのですが、こちらのページに@react-three/fiberを使った作品がいっぱい載っていて面白かったので、是非見てみて下さい!
https://r3f.docs.pmnd.rs/getting-started/examples