Three.js 3D スタック
2026-10 時点で確認済み: three 0.186, @react-three/fiber 9.8, @react-three/drei 10.7, @react-three/rapier 2.2, @react-three/postprocessing 3.2 (+ postprocessing 6.39)。Fiber 9 / Drei 10 / Rapier 2 / postprocessing 3 には React 19 が必要です(fiber 9.8 のピア依存: react >=19 <19.4)。
npm i three @react-three/fiber @react-three/drei
npm i @react-three/rapier # 物理演算
npm i @react-three/postprocessing postprocessing # エフェクト
npm i -D @types/three
ベースシーン (Web)
import { useRef, Suspense } from 'react'
import * as THREE from 'three'
import { Canvas, useFrame } from '@react-three/fiber'
import { OrbitControls, Environment, ContactShadows } from '@react-three/drei'
function Box() {
const ref = useRef<THREE.Mesh>(null!)
useFrame((_, delta) => { ref.current.rotation.y += delta }) // フレームごとに ref を変更し、setState は絶対に使わない
return (
<mesh ref={ref} castShadow>
<boxGeometry />
<meshStandardMaterial color="#4a9eff" />
</mesh>
)
}
<Canvas camera={{ position: [3, 2, 5], fov: 45 }} dpr={[1, 2]} shadows>
<ambientLight intensity={0.3} />
<directionalLight position={[5, 5, 5]} castShadow />
<Suspense fallback={null}>
<Box />
<Environment preset="city" />
</Suspense>
<ContactShadows position={[0, -0.5, 0]} opacity={0.5} blur={2} />
<OrbitControls />
</Canvas>
ルール:
- 非同期処理 (useGLTF, useTexture, Environment, Text フォント, Physics WASM) はすべて
<Suspense>の下に配置します。 - 初回描画で使用するモデルは、モジュールレベルで
useGLTF.preload(url)を呼び出します。 - シーンのサイズは 1〜10 単位に保ちます。極端に大きい/小さいスケールはシャドウやコントロールを破壊します。
MeshStandardMaterialにはライトか<Environment>が必要です。どちらもないと黒く表示されます。
Drei (必要なものだけを選択)
- カメラ/コントロール:
OrbitControls,CameraControls,PerspectiveCamera makeDefault - アセット:
useGLTF,useTexture,Environment(プリセット: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse),Center,Bounds - ステージング:
ContactShadows,AccumulativeShadows,Stage,Float,Sparkles,Stars,Cloud - マテリアル:
MeshTransmissionMaterial(ガラス),MeshReflectorMaterial(反射する床) - テキスト:
Text(SDF、任意のフォント URL),Text3D(タイプフェース JSON が必要),Html(DOM オーバーレイ)
Rapier 物理演算
import { Physics, RigidBody, CuboidCollider, type RapierRigidBody } from '@react-three/rapier'
<Suspense>
<Physics gravity={[0, -9.81, 0]} /* debug */>
<RigidBody colliders="hull" restitution={0.6} ref={bodyRef}>
<mesh><torusGeometry /><meshStandardMaterial /></mesh>
</RigidBody>
<RigidBody type="fixed">
<CuboidCollider args={[10, 0.1, 10]} /> {/* 半サイズ */}
</RigidBody>
</Physics>
</Suspense>
// bodyRef.current?.applyImpulse({ x: 0, y: 5, z: 0 }, true)
colliders:"cuboid"(デフォルト),"ball","hull","trimesh",false(自分でコライダーコンポーネントを追加)。type:"dynamic"(デフォルト),"fixed","kinematicPosition","kinematicVelocity"。timeStepはデフォルトで固定 1/60 です。timeStep="vary"は非決定的です。手動ステップ:useRapier().step(1 / 60)。<Physics>にdebugを付けるとコライダーが描画されます。
ポストプロセッシング
import { EffectComposer, Bloom, Vignette, DepthOfField, Noise } from '@react-three/postprocessing'
<EffectComposer>{/* <Canvas> の最後の子要素 */}
<Bloom luminanceThreshold={0.9} intensity={1.2} mipmapBlur />
<Vignette offset={0.1} darkness={0.9} />
</EffectComposer>
- Bloom は
luminanceThreshold以上のピクセルだけをエフェクト対象とします。1 つのオブジェクトを光らせるには: マテリアルにemissiveカラー +emissiveIntensity> 1 +toneMapped={false}を設定します。 - 各エフェクトはフルスクリーンパスを消費します。2〜4 エフェクトで十分です。
動画向け
Remotion (remotion-markup/3d.md を参照): @remotion/three から <ThreeCanvas width height> を使用します。useFrame は使わず、すべてのモーションは useCurrentFrame() から駆動します。さらに:
OrbitControlsは使用しません。カメラはframeから移動させます (interpolate/spring)。- 独自のクロックでアニメーションする Drei ヘルパー (
Float,Sparkles,Stars,Cloud,MeshWobbleMaterial) はレンダリング時にちらつきます。これらは使用しないか、モーションをframeから再現します。 - 物理演算: Remotion はタブ間でフレームを順不同でレンダリングするため、ライブシミュレーションは一致しません。ベイクしてください:
@dimforge/rapier3d-compat(@react-three/rapierの基盤エンジン) を一度初期化し、各フレームに対してworld.step()をtimestep = 1 / fpsで実行し、各ボディの位置と回転をフレームごとに保存します。その後、インデックスframeの変換をレンダリングします。
Remotion 以外 (画面録画、GSAP 駆動) の場合: useFrame は問題ありません。ただし、ショットを繰り返し可能にする必要がある場合は、delta の代わりにタイムライン (tl.time()) から時間を駆動します。