React Three Fiber 完全ガイド - Reactで3Dグラフィックスを宣言的に構築

React Three Fiber 完全ガイド - Reactで3Dグラフィックスを宣言的に構築

作成日:

Three.jsとReact Three Fiberとは

Three.js

Three.jsは、WebGLを使用してブラウザ上で3Dグラフィックスを描画するためのJavaScriptライブラリです。複雑なWebGL APIを抽象化し、直感的なAPIで3Dシーンを構築できます。

React Three Fiber (R3F)

React Three Fiberは、Three.jsのReactレンダラーです。Reactの宣言的なコンポーネントモデルで3Dシーンを構築でき、状態管理やフックも自然に使用できます。

なぜR3Fを使うのか

  • 宣言的: JSXで3Dオブジェクトを記述
  • Reactエコシステム: useState、useEffect、useRefがそのまま使える
  • パフォーマンス: 自動的な最適化とリアクティブな更新
  • 豊富なエコシステム: Drei(ヘルパーライブラリ)による機能拡張

インストール

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

基本的な使い方

Canvasコンポーネント

R3Fのシーンは<Canvas>コンポーネント内に記述します。

TypeScript
import { Canvas } from '@react-three/fiber';
 
function App() {
  return (
    <Canvas camera={{ position: [3, 3, 3], fov: 50 }}>
      <ambientLight intensity={0.5} />
      <pointLight position={[10, 10, 10]} />
      <mesh>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color="#667eea" />
      </mesh>
    </Canvas>
  );
}

基本的な3Dシーン

※ マウスドラッグで回転、スクロールでズーム

<Canvas camera={{ position: [3, 3, 3] }}> <ambientLight intensity={0.5} /> <mesh> <boxGeometry args={[1.5, 1.5, 1.5]} /> <meshStandardMaterial color="#667eea" /> </mesh> <OrbitControls /> </Canvas>

基本構造

R3Fでは、Three.jsのオブジェクトがそのままJSXコンポーネントになります:

Three.jsR3F (JSX)
new THREE.Mesh()<mesh>
new THREE.BoxGeometry(1, 1, 1)<boxGeometry args={[1, 1, 1]} />
new THREE.MeshStandardMaterial({ color: 'red' })<meshStandardMaterial color="red" />
new THREE.PointLight()<pointLight />

ジオメトリ

Three.jsには多くの組み込みジオメトリがあります。argsプロパティでコンストラクタ引数を渡します。

TypeScript
// 立方体
<boxGeometry args={[, 高さ, 奥行き]} />
 
// 球体
<sphereGeometry args={[半径, 水平セグメント, 垂直セグメント]} />
 
// 円錐
<coneGeometry args={[半径, 高さ, セグメント]} />
 
// 円柱
<cylinderGeometry args={[上半径, 下半径, 高さ, セグメント]} />
 
// トーラス(ドーナツ)
<torusGeometry args={[半径, チューブ半径, セグメント, チューブセグメント]} />
 
// トーラスノット
<torusKnotGeometry args={[半径, チューブ半径, チューブセグメント, 放射セグメント]} />

ジオメトリの種類

BoxSphereConeCylinderTorusTorusKnotDodecaOcta

カスタムジオメトリ

頂点を直接定義してカスタムジオメトリを作成することもできます:

TypeScript
import { useMemo } from 'react';
import * as THREE from 'three';
 
function CustomGeometry() {
  const geometry = useMemo(() => {
    const geo = new THREE.BufferGeometry();
    const vertices = new Float32Array([
      0, 1, 0,   // 頂点1
      -1, -1, 0, // 頂点2
      1, -1, 0   // 頂点3
    ]);
    geo.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
    return geo;
  }, []);
 
  return (
    <mesh geometry={geometry}>
      <meshBasicMaterial color="red" side={THREE.DoubleSide} />
    </mesh>
  );
}

マテリアル

マテリアルは3Dオブジェクトの見た目を決定します。用途に応じて適切なマテリアルを選択しましょう。

マテリアルの種類

Basic

ライティング無視

Lambert

拡散反射のみ

Phong

鏡面反射あり

Standard

PBR標準

Physical

PBR高品質

Toon

セルシェーディング

Normal

法線可視化

Matcap

テクスチャベース

マテリアルの種類

マテリアル特徴用途
meshBasicMaterialライティング無視ワイヤーフレーム、UI要素
meshLambertMaterial拡散反射のみマットな表面、低負荷
meshPhongMaterial鏡面反射ありプラスチック、光沢表面
meshStandardMaterialPBR標準一般的な用途(推奨)
meshPhysicalMaterialPBR高品質ガラス、クリアコート
meshToonMaterialセルシェーディングカートゥーン調
meshNormalMaterial法線を色で表示デバッグ用

MeshStandardMaterial(推奨)

物理ベースレンダリング(PBR)の標準マテリアルです:

TypeScript
<meshStandardMaterial
  color="#667eea"
  metalness={0.5}    // 金属度 (0-1)
  roughness={0.3}    // 粗さ (0-1)
  envMapIntensity={1} // 環境マップ強度
/>

MeshPhysicalMaterial(高品質)

より高度な物理特性を表現できます:

TypeScript
<meshPhysicalMaterial
  color="#88ccff"
  metalness={0}
  roughness={0}
  transmission={0.9}     // 透過度(ガラス)
  thickness={0.5}        // 厚さ
  clearcoat={1}          // クリアコート
  clearcoatRoughness={0} // クリアコート粗さ
/>

ライティング

適切なライティングは3Dシーンの品質を大きく左右します。

ライティングの種類

※ ライトをクリックしてON/OFFを切り替え

ライトの種類

TypeScript
// 環境光 - シーン全体を均一に照らす
<ambientLight intensity={0.5} color="#ffffff" />
 
// ポイントライト - 全方向に光を放射
<pointLight position={[10, 10, 10]} intensity={1} color="#ffffff" />
 
// スポットライト - 円錐形に光を放射
<spotLight
  position={[5, 5, 5]}
  intensity={1}
  angle={0.3}      // 光の角度
  penumbra={0.5}   // ぼかし
  castShadow       // 影を落とす
/>
 
// ディレクショナルライト - 平行光線(太陽光)
<directionalLight
  position={[5, 5, 5]}
  intensity={1}
  castShadow
/>
 
// ヘミスフィアライト - 空と地面からの光
<hemisphereLight
  skyColor="#87ceeb"
  groundColor="#362312"
  intensity={0.5}
/>

影の設定

影を有効にするには、Canvas、ライト、オブジェクトそれぞれに設定が必要です:

TypeScript
<Canvas shadows>
  <directionalLight castShadow />
  <mesh castShadow receiveShadow>
    <boxGeometry />
    <meshStandardMaterial />
  </mesh>
  <mesh receiveShadow rotation={[-Math.PI / 2, 0, 0]}>
    <planeGeometry args={[10, 10]} />
    <meshStandardMaterial />
  </mesh>
</Canvas>

アニメーション - useFrame

useFrameフックは毎フレーム実行されるコールバックを登録します。これがR3Fのアニメーションの基本です。

TypeScript
import { useFrame } from '@react-three/fiber';
import { useRef } from 'react';
import * as THREE from 'three';
 
function RotatingBox() {
  const meshRef = useRef<THREE.Mesh>(null!);
 
  useFrame((state, delta) => {
    // delta: 前フレームからの経過時間(秒)
    meshRef.current.rotation.x += delta;
    meshRef.current.rotation.y += delta * 0.5;
 
    // state.clock.elapsedTime: 開始からの経過時間
    meshRef.current.position.y = Math.sin(state.clock.elapsedTime) * 0.5;
  });
 
  return (
    <mesh ref={meshRef}>
      <boxGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color="orange" />
    </mesh>
  );
}

useFrameアニメーション

インスタンシングによるパーティクル波

オービットとパルスアニメーション

useFrame((state, delta) => { // state.clock.elapsedTime - 経過時間 // delta - 前フレームからの差分 meshRef.current.rotation.y += delta; meshRef.current.position.y = Math.sin(state.clock.elapsedTime); });

useFrameの注意点

  • useFrame内でsetStateを呼ばない: 毎フレーム再レンダリングが発生してしまう
  • refを使用する: 直接オブジェクトのプロパティを変更する
  • deltaを使用する: フレームレートに依存しないアニメーション

インスタンシング

大量のオブジェクトを効率的にレンダリングするにはInstancedMeshを使用します:

TypeScript
function Particles({ count = 1000 }) {
  const meshRef = useRef<THREE.InstancedMesh>(null!);
  const dummy = useMemo(() => new THREE.Object3D(), []);
 
  useFrame((state) => {
    for (let i = 0; i < count; i++) {
      const t = state.clock.elapsedTime + i * 0.1;
      dummy.position.set(
        Math.sin(t) * 2,
        Math.cos(t * 0.5) * 2,
        Math.sin(t * 0.3) * 2
      );
      dummy.updateMatrix();
      meshRef.current.setMatrixAt(i, dummy.matrix);
    }
    meshRef.current.instanceMatrix.needsUpdate = true;
  });
 
  return (
    <instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
      <sphereGeometry args={[0.05, 16, 16]} />
      <meshStandardMaterial color="cyan" />
    </instancedMesh>
  );
}

インタラクション

R3Fでは、HTML要素と同じようにイベントハンドラを設定できます。

TypeScript
function InteractiveBox() {
  const [hovered, setHovered] = useState(false);
  const [clicked, setClicked] = useState(false);
 
  return (
    <mesh
      onClick={() => setClicked(!clicked)}
      onPointerOver={() => setHovered(true)}
      onPointerOut={() => setHovered(false)}
      scale={clicked ? 1.5 : 1}
    >
      <boxGeometry />
      <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
    </mesh>
  );
}

インタラクション

🖱️ ホバー

スケール&発光

👆 クリック

回転ON/OFF

✋ ドラッグ

球体を移動

利用可能なイベント

イベント説明
onClickクリック時
onContextMenu右クリック時
onDoubleClickダブルクリック時
onPointerOverホバー開始時
onPointerOutホバー終了時
onPointerDownポインタ押下時
onPointerUpポインタ離す時
onPointerMoveポインタ移動時
onWheelスクロール時

Dreiライブラリ

@react-three/dreiは、R3Fの公式ヘルパーライブラリです。よく使う機能がコンポーネント化されています。

OrbitControls(カメラ操作)

マウスでカメラを回転・ズーム・パンできるようにします:

TypeScript
import { OrbitControls } from '@react-three/drei';
 
<Canvas>
  <OrbitControls
    enableDamping      // 慣性
    dampingFactor={0.05}
    minDistance={2}    // 最小ズーム距離
    maxDistance={20}   // 最大ズーム距離
    enablePan={false}  // パン無効
    autoRotate         // 自動回転
    autoRotateSpeed={0.5}
  />
</Canvas>

Environment(環境マッピング)

HDR環境マップを簡単に設定できます:

TypeScript
import { Environment } from '@react-three/drei';
 
<Canvas>
  <Environment
    preset="sunset"      // プリセット: city, sunset, studio, forest, etc.
    background           // 背景として表示
    backgroundBlurriness={0.3}
  />
</Canvas>

環境マッピング & リフレクション

左: ガラス(transmission) / 中央: 鏡面反射(metalness=1) / 右: ゴールド

ContactShadows(接地影)

リアルな接地影を簡単に追加できます:

TypeScript
import { ContactShadows } from '@react-three/drei';
 
<ContactShadows
  position={[0, -0.5, 0]}
  opacity={0.5}
  scale={10}
  blur={1}
  far={10}
/>

Float(浮遊アニメーション)

オブジェクトを自然に浮遊させます:

TypeScript
import { Float } from '@react-three/drei';
 
<Float
  speed={2}              // アニメーション速度
  rotationIntensity={0.5} // 回転の強さ
  floatIntensity={0.5}   // 浮遊の強さ
>
  <mesh>
    <sphereGeometry />
    <meshStandardMaterial />
  </mesh>
</Float>

Text3D(3Dテキスト)

3Dテキストを生成します(フォントファイルが必要):

TypeScript
import { Text3D, Center } from '@react-three/drei';
 
<Center>
  <Text3D
    font="/fonts/helvetiker_bold.typeface.json"
    size={1}
    height={0.2}
    bevelEnabled
    bevelThickness={0.02}
    bevelSize={0.02}
  >
    Hello 3D
    <meshStandardMaterial color="#667eea" />
  </Text3D>
</Center>

3Dテキスト (SDF Text)

import { Text } from '@react-three/drei'; <Text fontSize={1.5} color="#667eea" anchorX="center" anchorY="middle" outlineWidth={0.05} outlineColor="#000000" > Hello 3D </Text>

※ DreiのTextコンポーネントはSDF(Signed Distance Field)レンダリングを使用。 Text3Dを使用する場合はフォントファイル(typeface.json形式)が必要です。

パフォーマンス最適化

3Dグラフィックスは計算負荷が高いため、パフォーマンス最適化は非常に重要です。特にモバイルデバイスや低スペックPCでも快適に動作させるために、以下のテクニックを活用しましょう。

1. フレームループの最適化

useFrame毎フレーム(60fps なら1秒間に60回)実行されるため、この中での処理を軽量に保つことが重要です。

問題点: newでオブジェクトを生成すると、毎フレームメモリ確保とガベージコレクションが発生し、パフォーマンスが低下します。

TypeScript
// ❌ Bad: useFrame内でオブジェクト生成
useFrame(() => {
  mesh.position.set(new THREE.Vector3(x, y, z)); // 毎フレームnew
});
 
// ✅ Good: 事前に作成したオブジェクトを再利用
const tempVec = useMemo(() => new THREE.Vector3(), []);
useFrame(() => {
  tempVec.set(x, y, z);
  mesh.position.copy(tempVec);
});

ポイント:

  • useMemoで一度だけオブジェクトを作成
  • useFrame内では既存オブジェクトの値を更新するだけ
  • setStateuseFrame内で呼ばない(再レンダリングが発生する)

2. ジオメトリの共有

同じ形状のオブジェクトを複数配置する場合、ジオメトリを共有することでメモリ使用量を大幅に削減できます。

問題点: JSXで<boxGeometry />を書くと、各メッシュごとに新しいジオメトリが作成されます。100個のボックスがあれば、100個のジオメトリがメモリに保持されます。

TypeScript
// ❌ Bad: 各インスタンスが個別のジオメトリ
{items.map((item) => (
  <mesh key={item.id}>
    <boxGeometry args={[1, 1, 1]} /> {/* 毎回新規作成 */}
  </mesh>
))}
 
// ✅ Good: ジオメトリを共有
const sharedGeometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []);
{items.map((item) => (
  <mesh key={item.id} geometry={sharedGeometry}>
    <meshStandardMaterial />
  </mesh>
))}

効果: 100個のオブジェクトでも、ジオメトリは1つだけメモリに保持されます。

3. インスタンシング

大量のオブジェクト(100〜10,000個以上)を表示する場合は、instancedMeshが最も効果的です。

通常のメッシュ: 1オブジェクト = 1ドローコール → 1000個 = 1000ドローコール インスタンシング: 同じジオメトリ・マテリアルなら1ドローコールで全て描画

TypeScript
// 1000個のオブジェクトを1回のドローコールで描画
<instancedMesh args={[geometry, material, count]}>
  {/* setMatrixAtで各インスタンスの位置・回転・スケールを設定 */}
</instancedMesh>

使用例:

  • パーティクルシステム(星空、雪、雨など)
  • 森や群衆のシーン
  • データビジュアライゼーション

4. LOD (Level of Detail)

カメラからの距離に応じてモデルの詳細度を切り替えるテクニックです。遠くのオブジェクトは低ポリゴンで十分なため、GPUの負荷を軽減できます。

TypeScript
import { Detailed } from '@react-three/drei';
 
<Detailed distances={[0, 50, 100]}>
  <HighDetailModel />   {/* 0〜50: 高詳細(10,000ポリゴン) */}
  <MediumDetailModel /> {/* 50〜100: 中詳細(1,000ポリゴン) */}
  <LowDetailModel />    {/* 100以上: 低詳細(100ポリゴン) */}
</Detailed>

使用シーン:

  • 広大なシーン(建物、地形、都市)
  • 複雑な3Dモデルを多数配置する場合

5. その他の最適化Tips

テクニック効果
gl.setPixelRatio(Math.min(devicePixelRatio, 2))Retinaディスプレイでの過剰な解像度を制限
frustumCulled={true}画面外のオブジェクトを描画しない(デフォルトで有効)
テクスチャサイズを2のべき乗にGPUメモリ効率が向上(512x512, 1024x1024など)
dispose()の呼び出し不要なジオメトリ・マテリアル・テクスチャを明示的に解放

よくあるパターン

ローディング表示

TypeScript
import { Suspense } from 'react';
import { Html } from '@react-three/drei';
 
function Loader() {
  return (
    <Html center>
      <div>Loading...</div>
    </Html>
  );
}
 
<Canvas>
  <Suspense fallback={<Loader />}>
    <HeavyModel />
  </Suspense>
</Canvas>

レスポンシブ対応

TypeScript
<Canvas
  style={{ width: '100%', height: '100vh' }}
  camera={{ position: [5, 5, 5] }}
  onCreated={({ gl }) => {
    gl.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  }}
>

背景色の設定

TypeScript
<Canvas>
  <color attach="background" args={['#1a1a2e']} />
  {/* ... */}
</Canvas>

まとめ

R3Fの強み

特徴説明
宣言的JSXで3Dシーンを記述できる
Reactとの統合フック、状態管理がそのまま使える
エコシステムDreiによる豊富なヘルパー
パフォーマンス自動最適化と効率的な更新

学習の順序

  1. 基本: Canvas、mesh、geometry、material
  2. ライティング: ambientLight、pointLight、shadows
  3. アニメーション: useFrame、useRef
  4. インタラクション: onClick、onPointerOver
  5. Drei: OrbitControls、Environment、etc.
  6. 最適化: インスタンシング、LOD、パフォーマンスチューニング

React Three Fiberを使いこなして、魅力的な3D体験をWebで実現しましょう!

参考リンク