React Three Fiber入門
ReactとThree.jsで始める3D表現

はてなブックマークへ投稿
65
Xへポスト
URLをコピー
共有

React Three Fiber」は、Three.jsをReactで扱うためのライブラリです。Reactの特徴である再利用可能なコンポーネントを活かしながら、宣言的に3Dシーンを構築できるのが大きな魅力です。

通常のThree.jsでは、メッシュの作成、マテリアルの適用、シーンへの追加などひとつひとつの処理を命令的に記述する必要があります。しかしReact Three Fiberを使えば、裏側の複雑な処理をライブラリ側が担ってくれるため、作りたいシーンをコンポーネントとして宣言でき、処理の流れがわかりやすいコードが書けます。

▼ 通常のThree.jsで立方体メッシュを記述する例

const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshNormalMaterial();
const box = new THREE.Mesh(geometry, material);
scene.add(box);

▼ React Three Fiberで立方体メッシュを記述する例

Canvasには手順3で作成するcreateWebGpuRendererglプロパティに渡します(以降の例も同様)。

<Canvas gl={createWebGpuRenderer}>
  <mesh>
    <boxGeometry />
    <meshNormalMaterial />
  </mesh>
</Canvas>

本記事ではその魅力と導入方法、簡単な実装例を紹介します。手続き型との違いやメリットを体感していただければと思います。

以下のような方にオススメです。

  • Three.jsとReactの基本的な書き方を知っている方
  • 通常のThree.jsの書き方がややこしくて苦手意識がある方

▼ React Three Fiberで実装した作例(ネジ巻きをクリックして遊んでみてください)

React Three Fiberとは

Three.jsのオブジェクトがReact用に適切にコンポーネント化されている点が、React Three Fiberの大きな特徴です。通常のThree.jsの書き方と比較すると、レンダラーやシーンの用意を省略でき、コード量を大幅に削減できるのが大きな魅力と言えます。また、コンポーネントベースのため構造が理解しやすく保守性でも優れています。

Three.jsの基礎を確認したい方は、『Three.js入門サイト』も参照ください。

参考:通常のThree.jsで表示するために必要な設定

<Canvas gl={createWebGpuRenderer}>
  <mesh>
    <boxGeometry />
    <meshNormalMaterial />
  </mesh>
</Canvas>

冒頭の例をもう一度見てみましょう。Canvasコンポーネントの中にmeshコンポーネントがあります。その中にジオメトリとマテリアルがツリー構造になっており、視覚的にも理解しやすいことが実感いただけるのではないでしょうか。

そのほか、meshコンポーネントがonClickonWheelなどイベント用のpropsをもつので、インタラクティブな実装を仕込むのが楽な点でも優秀です。

いい事づくしでReactプロジェクトなら採用しない手はないですね! それでは、ライブラリ導入から3Dの表示までの手順を解説します。

①ライブラリの導入

手順1. 事前準備(Reactのプロジェクト作る)

まず、Reactプロジェクトを作る必要があります。今回はVite + React + TypeScriptでプロジェクトを作成しました。以下リンクのサンプルコードをクローンしたり、コマンドラインで新規にプロジェクトを作成しリポジトリを用意しましょう。

▼ Viteで新しくプロジェクトを作る場合

npm create vite@latest
  • 「Select a framework:」は「React」を選択してください。
  • 「Select a variant:」は「TypeScript」を選択してください。

手順2. ライブラリのインストール

Three.js本体と型情報、@react-three/fiberをインストールします。

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

手順3. WebGPUレンダラーを指定する

WebGPUは、従来のWebGLに代わる新世代のグラフィックスAPIです。効率的にGPUへアクセスでき、WebGLより高いパフォーマンスが期待できます。Three.jsにはWebGPU対応のレンダラーWebGPURendererが用意されています。

WebGPU対応のThree.jsの基本は、記事『WebGPU対応のThree.jsのはじめ方』で解説しています。

React Three FiberのCanvasは、デフォルトではWebGLRendererを使います。Three.jsのWebGPURendererを使うには、glプロパティに非同期関数を渡します。WebGPURendererは初期化にinit()が必要ですが、React Three Fiberが待機処理を担ってくれます。

複数のサンプルで使い回せるよう、レンダラーを生成する関数をファイルとして切り出しておきます。

src/lib/createWebGpuRenderer.ts

import { WebGPURenderer, type WebGPURendererParameters } from "three/webgpu";

// React Three FiberのCanvasのgl propに渡すrenderer factory
export const createWebGpuRenderer = async (props: object) => {
  // React Three FiberのpropsはWebGL基準の型なので、WebGPU rendererの型として読み替える
  const renderer = new WebGPURenderer(props as WebGPURendererParameters);

  // WebGPUは非同期で初期化
  await renderer.init();

  return renderer;
};

以降のサンプルでは、この関数をインポートしてCanvasglプロパティに渡します。

※この書き方は執筆時点のReact Three Fiber 9系のものです。開発中のv10ではWebGPUが標準対応となり、初期化関数を書かずにCanvasrendererプロパティで指定できるようになる予定です。詳しくはReact Three Fiber v10 alpha discussionを参照ください。

②コンポーネントの追加

それでは早速表示させてみましょう。画面に表示したいコンポーネントにCanvasコンポーネントを追加します。その中にmeshコンポーネントを追加し、ジオメトリとマテリアルのコンポーネントも追加します。

import { Canvas } from "@react-three/fiber";
// 手順3で作成したレンダラー生成関数
import { createWebGpuRenderer } from "../../lib/createWebGpuRenderer";

<Canvas gl={createWebGpuRenderer}>
  <mesh>
    {/* 球体ジオメトリ */}
    <sphereGeometry />
    {/* ノーマルマテリアル */}
    <meshNormalMaterial />
  </mesh>
</Canvas>

球体の表示サンプル

これだけで表示できます。カメラなどカスタマイズしたい場合は、Canvasコンポーネントのプロパティを調整できます。

▼ カメラと影を調整する例

<Canvas
  gl={createWebGpuRenderer}
  camera={{
    fov: 45, // 視野角
    position: [-8, 3, 8], // 位置
  }}
  shadows="soft" // 影を有効化
></Canvas>

外部3DモデルはSuspenseでラップして読み込む

3Dモデルを読み込んでみましょう。モデルのデータはpublicディレクトリ配下に置いておき、useLoader(ローダー, データのパス)でモデルを読み込みます。今回はglTF形式なので、GLTFLoaderを使います。

primitiveコンポーネントのobjectプロパティに読み込んだデータのシーンを渡します。

src/pages/model/Page.tsxの抜粋

import { Suspense } from "react";
import { Canvas, useLoader } from "@react-three/fiber";
import { createWebGpuRenderer } from "../../lib/createWebGpuRenderer";
import { GLTFLoader } from "three/examples/jsm/Addons.js";

const Model = () => {
  // 3Dモデルの読み込み
  const gltf = useLoader(GLTFLoader, "./gltf/neji.glb");
  return (
    <primitive
      object={gltf.scene}
      scale={2}
      position={[0, 0.6, 0]}
      rotation={[0, 0.7, 0]}
    />
  );
};

export const ModelPage = () => (
  <Canvas gl={createWebGpuRenderer}>
    <Suspense fallback={null}>
      <Model />
    </Suspense>
  </Canvas>
);

▼ モデルの読み込みを待たず、シーンが読み込まれる

3Dモデルの読み込み

後述しますが、ライブラリ@react-three/dreiを導入しておくと、より簡潔に記述できるので検討するとよいかもしれません。

Gltfコンポーネントを利用した書き方例

import { Gltf } from "@react-three/drei";

const Model = () => {
  return <Gltf src="./gltf/neji.glb" />;
};

③インタラクション

インタラクションを追加してみましょう。meshコンポーネントはonClickonPointerOveronPointerMoveなどさまざまなイベント用のpropsをもっています。Three.jsなどCanvas内の要素は通常、DOMのようなイベントは受け付けませんが、内部的にレイキャスター(オブジェクトに光線が当たったかを検出する方法)が使用され、propsとしてイベントが発火できるようになっています。

src/pages/pointer/Cube.tsxの抜粋

export const Cube = ({ disablePropagated, ...props }: Props) => {
  const [isActive, setIsActive] = useState(false);

  const handlePointerOver = (event: ThreeEvent<PointerEvent>) => {
    if (disablePropagated) {
      // 手前のオブジェクトでイベントが発生したら伝播を止める
      event.stopPropagation();
    }
    setIsActive(true);
  };

  const handlePointerOut = (event: ThreeEvent<PointerEvent>) => {
    if (disablePropagated) {
      event.stopPropagation();
    }
    setIsActive(false);
  };

  return (
    <mesh {...props} scale={1.4} onPointerOver={handlePointerOver} onPointerOut={handlePointerOut}>
      <boxGeometry />
      <meshStandardMaterial color={isActive ? "#02bbff" : "#edce3c"} />
    </mesh>
  );
};

注意点として、ポインター系のイベントは重なっているオブジェクトを貫通して発火します。

ポインターイベント伝播の比較

ポインターイベントを貫通させたくない場合、オブジェクトのイベントハンドラーにevent.stopPropagation()を追加することで、ポインターイベントの伝播を防止できます。

const handlePointerOver = (event: ThreeEvent<PointerEvent>) => {
  // 手前のオブジェクトでイベントが発生したら伝播を止める
  event.stopPropagation();
};

④アニメーション

アニメーション実装例

アニメーション① クリックしたら回転させる例

クリック時にアニメーションを追加してみましょう。アニメーションの実装にはuseFramerequestAnimationFrameのようなフック)が利用できます。

注意点として、useFrame内ではフレームごとの状態更新にsetStateを使わないようにしましょう。毎フレーム変わるメッシュのプロパティはrefで参照して更新し、setStateはクリックやアニメーション完了時のような状態切り替えにとどめます。

今回はmeshRef.current.rotation.yプロパティを更新する処理を追加しました。メッシュをクリックしてisActivetrueになった時に、1回転するアニメーションが行われます。

▼ クリック時にメッシュを回転させる例の抜粋

const Box = () => {
  // 回転アニメーションがアクティブか?
  const [isActive, setIsActive] = useState(false);
  // メッシュの参照
  const meshRef = useRef<Mesh>(null);

  // 毎フレームの更新
  useFrame((state, delta) => {
    if (!meshRef.current) {
      return;
    }
    // クリック時(isActiveがtrueの時)に1回転させる
    meshRef.current.rotation.y = isActive
      ? THREE.MathUtils.damp(
          meshRef.current.rotation.y, // from
          2 * Math.PI, // to
          4, //減衰係数。値が大きいほど動きが急になり、小さいほど動きがなめらかになる
          delta, // 補間係数。リフレッシュレートに依存しないアニメーション速度を保つためデルタタイムを渡す
        )
      : 0; // 0に戻す

    // 回転が終わった時の処理
    if (meshRef.current.rotation.y >= 2 * Math.PI - 0.01) {
      setIsActive(false);
    }
  });

  // クリック時の処理
  const handleClick = () => {
    setIsActive(true);
  };

  return (
    <mesh
      ref={meshRef}
      rotation={[-1, 0, 1]}
      position={[0, 15, 0]} // 初期位置上から登場するcastShadow={true} // 影を落とす
      onClick={handleClick}
    >
      <boxGeometry />
      <meshStandardMaterial />
    </mesh>
  );
};

アニメーション② ポインターの位置に応じてメッシュの座標を動かす例

マウスやポインターの位置に応じて、メッシュの位置を移動する処理を追加します。

▼ ポインターの位置に応じてメッシュを動かす例の抜粋

const Box = () => {
  // メッシュの参照
  const meshRef = useRef<Mesh>(null);

  const v = new Vector3();
  // 毎フレームの更新
  useFrame((state, delta) => {
    if (!meshRef.current) {
      return;
    }

    // ポインターの位置に応じてメッシュのxy座標をなめらかに動かす
    meshRef.current.position.lerp(
      v.set(state.pointer.x * 3, state.pointer.y * 2, 0),
      delta * 2, // 補間係数。リフレッシュレートに依存しないアニメーション速度を保つためデルタタイムを渡す
    );
  });

  return (
    // 省略
  );
};

useFrameフックはいくつかの引数をとります。stateにはステージの情報が含まれており、マウスやポインターの位置に応じてプロパティを更新したい場合は、state.pointerで参照できます。

コラム: エコシステムについて

React Three Fiberは開発に役立つエコシステムが充実しています。とくにオススメのライブラリを紹介します。

@react-three/drei

@react-three/dreiは、より複雑な表現をしたい場合、導入しておくと非常に役立つであろうヘルパーライブラリです。OrbitControlsなどステージの制御に使える機能や、グラデーション等のマテリアル(シェーダー)、少し凝ったコンポーネント(sky, caustics)などさまざまな機能が提供されています。

どのような機能があるかは、公式ドキュメントやStorybookが参考になります。

▼ ライブラリ追加

npm install @react-three/drei

▼ 実装例

まとめ

React Three Fiberの導入方法から実装方法までを紹介しました。Reactの基本的な書き方に慣れている方は、コードがシンプルかつ直感的に書け、コンポーネントの分割による管理のしやすさも実感できたのではないでしょうか。

Reactの再レンダリングと3Dの描画が絡むため、パフォーマンスのチューニングについて注意しておけると安心です。公式ドキュメントのパフォーマンスの落とし穴をぜひご一読ください。

React Three Fiberは私たちのプロジェクトでも採用して、開発体験の向上を感じています。ぜひ、React Three Fiberを導入したアプリケーション開発を行ってみてください!

※この記事が公開されたのは1年前ですが、今月7月に内容をメンテナンスしています。

SNSでシェアしよう
シェアいただくと、サイト運営の励みになります!
はてなブックマークへ投稿
Xへポスト
URLをコピー
共有