
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(ヘルパーライブラリ)による機能拡張
インストール
npm install three @react-three/fiber @react-three/drei @types/three基本的な使い方
Canvasコンポーネント
R3Fのシーンは<Canvas>コンポーネント内に記述します。
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.js | R3F (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プロパティでコンストラクタ引数を渡します。
// 立方体
<boxGeometry args={[幅, 高さ, 奥行き]} />
// 球体
<sphereGeometry args={[半径, 水平セグメント, 垂直セグメント]} />
// 円錐
<coneGeometry args={[半径, 高さ, セグメント]} />
// 円柱
<cylinderGeometry args={[上半径, 下半径, 高さ, セグメント]} />
// トーラス(ドーナツ)
<torusGeometry args={[半径, チューブ半径, セグメント, チューブセグメント]} />
// トーラスノット
<torusKnotGeometry args={[半径, チューブ半径, チューブセグメント, 放射セグメント]} />ジオメトリの種類
カスタムジオメトリ
頂点を直接定義してカスタムジオメトリを作成することもできます:
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 | 鏡面反射あり | プラスチック、光沢表面 |
meshStandardMaterial | PBR標準 | 一般的な用途(推奨) |
meshPhysicalMaterial | PBR高品質 | ガラス、クリアコート |
meshToonMaterial | セルシェーディング | カートゥーン調 |
meshNormalMaterial | 法線を色で表示 | デバッグ用 |
MeshStandardMaterial(推奨)
物理ベースレンダリング(PBR)の標準マテリアルです:
<meshStandardMaterial
color="#667eea"
metalness={0.5} // 金属度 (0-1)
roughness={0.3} // 粗さ (0-1)
envMapIntensity={1} // 環境マップ強度
/>MeshPhysicalMaterial(高品質)
より高度な物理特性を表現できます:
<meshPhysicalMaterial
color="#88ccff"
metalness={0}
roughness={0}
transmission={0.9} // 透過度(ガラス)
thickness={0.5} // 厚さ
clearcoat={1} // クリアコート
clearcoatRoughness={0} // クリアコート粗さ
/>ライティング
適切なライティングは3Dシーンの品質を大きく左右します。
ライティングの種類
※ ライトをクリックしてON/OFFを切り替え
ライトの種類
// 環境光 - シーン全体を均一に照らす
<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、ライト、オブジェクトそれぞれに設定が必要です:
<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のアニメーションの基本です。
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を使用します:
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要素と同じようにイベントハンドラを設定できます。
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(カメラ操作)
マウスでカメラを回転・ズーム・パンできるようにします:
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環境マップを簡単に設定できます:
import { Environment } from '@react-three/drei';
<Canvas>
<Environment
preset="sunset" // プリセット: city, sunset, studio, forest, etc.
background // 背景として表示
backgroundBlurriness={0.3}
/>
</Canvas>環境マッピング & リフレクション
ContactShadows(接地影)
リアルな接地影を簡単に追加できます:
import { ContactShadows } from '@react-three/drei';
<ContactShadows
position={[0, -0.5, 0]}
opacity={0.5}
scale={10}
blur={1}
far={10}
/>Float(浮遊アニメーション)
オブジェクトを自然に浮遊させます:
import { Float } from '@react-three/drei';
<Float
speed={2} // アニメーション速度
rotationIntensity={0.5} // 回転の強さ
floatIntensity={0.5} // 浮遊の強さ
>
<mesh>
<sphereGeometry />
<meshStandardMaterial />
</mesh>
</Float>Text3D(3Dテキスト)
3Dテキストを生成します(フォントファイルが必要):
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でオブジェクトを生成すると、毎フレームメモリ確保とガベージコレクションが発生し、パフォーマンスが低下します。
// ❌ 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内では既存オブジェクトの値を更新するだけsetStateをuseFrame内で呼ばない(再レンダリングが発生する)
2. ジオメトリの共有
同じ形状のオブジェクトを複数配置する場合、ジオメトリを共有することでメモリ使用量を大幅に削減できます。
問題点: JSXで<boxGeometry />を書くと、各メッシュごとに新しいジオメトリが作成されます。100個のボックスがあれば、100個のジオメトリがメモリに保持されます。
// ❌ 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ドローコールで全て描画
// 1000個のオブジェクトを1回のドローコールで描画
<instancedMesh args={[geometry, material, count]}>
{/* setMatrixAtで各インスタンスの位置・回転・スケールを設定 */}
</instancedMesh>使用例:
- パーティクルシステム(星空、雪、雨など)
- 森や群衆のシーン
- データビジュアライゼーション
4. LOD (Level of Detail)
カメラからの距離に応じてモデルの詳細度を切り替えるテクニックです。遠くのオブジェクトは低ポリゴンで十分なため、GPUの負荷を軽減できます。
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()の呼び出し | 不要なジオメトリ・マテリアル・テクスチャを明示的に解放 |
よくあるパターン
ローディング表示
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>レスポンシブ対応
<Canvas
style={{ width: '100%', height: '100vh' }}
camera={{ position: [5, 5, 5] }}
onCreated={({ gl }) => {
gl.setPixelRatio(Math.min(window.devicePixelRatio, 2));
}}
>背景色の設定
<Canvas>
<color attach="background" args={['#1a1a2e']} />
{/* ... */}
</Canvas>まとめ
R3Fの強み
| 特徴 | 説明 |
|---|---|
| 宣言的 | JSXで3Dシーンを記述できる |
| Reactとの統合 | フック、状態管理がそのまま使える |
| エコシステム | Dreiによる豊富なヘルパー |
| パフォーマンス | 自動最適化と効率的な更新 |
学習の順序
- 基本: Canvas、mesh、geometry、material
- ライティング: ambientLight、pointLight、shadows
- アニメーション: useFrame、useRef
- インタラクション: onClick、onPointerOver
- Drei: OrbitControls、Environment、etc.
- 最適化: インスタンシング、LOD、パフォーマンスチューニング
React Three Fiberを使いこなして、魅力的な3D体験をWebで実現しましょう!