基本(Canvas)
R3F のすべては <Canvas> から始まる。これが Three.js の renderer / scene / camera /
renderloop / イベントシステムを一括で立ち上げる入口。中の JSX が Three.js のシーングラフになる。
最小例
import { Canvas } from "@react-three/fiber"
export default function App() {
return (
<Canvas style={{ height: "100vh" }}>
<ambientLight intensity={0.6} />
<directionalLight position={[3, 5, 4]} />
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
</Canvas>
)
}
これだけで:
WebGLRendererが作られて canvas 要素に接続されるSceneが作られ、JSX のツリーがscene.addされる- デフォルトの
PerspectiveCamera(FOV 75、position [0,0,5])が用意される - 毎フレーム
requestAnimationFrameでレンダリングが走る - イベントシステム(pointer events / raycaster)がセットアップされる
<Canvas> の主な props
| prop | 型 | 役割 |
|---|---|---|
camera | object | デフォルトカメラの初期化オプション |
orthographic | boolean | true で OrthographicCamera に切替 |
gl | object | WebGLRenderer | (canvas) => renderer | WebGLRenderer のオプション or 自前生成 |
shadows | boolean | "soft" | "basic" | "percentage" | "variance" | シャドウマップタイプ |
dpr | number | [min, max] | デバイスピクセル比。[1, 2] で clamp が定石 |
flat | boolean | true で tone mapping を無効化 |
linear | boolean | true で linear color space |
frameloop | "always" | "demand" | "never" | レンダーループの挙動 |
onCreated | (state) => void | 初期化完了直後のコールバック |
onPointerMissed | (e) => void | シーン内何もない場所のクリック |
events | EventManager | イベントシステムの差し替え |
raycaster | RaycasterParameters | raycaster オプション |
scene | SceneProps | scene.background 等 |
resize | ResizeOptions | リサイズ動作の調整 |
performance | { current, min, max, debounce } | 性能ターゲット(regress 機構) |
legacy | boolean | 旧来のカラースペース挙動 |
camera の中身
<Canvas
camera={{
position: [3, 2, 5],
fov: 50,
near: 0.1,
far: 100,
up: [0, 1, 0],
zoom: 1,
}}
>
これで作られるのはあくまで「初期値」。後からカメラを差し替える時はシーン構築の
<PerspectiveCamera makeDefault /> パターンを使う。
gl の主なオプション
<Canvas
gl={{
antialias: true,
alpha: false, // true で背景透明
powerPreference: "high-performance",
stencil: false,
depth: true,
toneMapping: THREE.ACESFilmicToneMapping,
toneMappingExposure: 1.0,
outputColorSpace: THREE.SRGBColorSpace, // 既定
preserveDrawingBuffer: false, // canvas のスクショを撮るなら true
logarithmicDepthBuffer: false, // 巨大シーン用
}}
>
gl に関数を渡すと WebGLRenderer 自体を自前で作れる:
<Canvas
gl={(canvas) => {
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true })
renderer.setClearColor("#0b0d12")
return renderer
}}
/>
onCreated
Canvas の準備が完了した直後に1回だけ呼ばれる。renderer の細かい設定や外部に state を漏らすのに便利。
<Canvas
onCreated={({ gl, scene, camera, raycaster }) => {
gl.setClearColor("#0b0d12")
scene.fog = new THREE.Fog("#0b0d12", 5, 30)
raycaster.params.Line.threshold = 0.1
}}
/>
よくある実用設定
<Canvas
shadows
dpr={[1, 2]}
camera={{ position: [3, 2, 5], fov: 50 }}
gl={{ antialias: true, toneMapping: THREE.ACESFilmicToneMapping }}
onCreated={({ gl }) => gl.setClearColor("#0b0d12")}
>
...
</Canvas>
JSX → Three.js の対応
Canvas の中では Three.js のクラス名の頭を小文字にした要素が全部使える。 R3F が Three.js の名前空間を walk して、JSX の type に対応するクラスを自動 register している。
| JSX | クラス |
|---|---|
<mesh> | THREE.Mesh |
<group> | THREE.Group |
<boxGeometry> | THREE.BoxGeometry |
<sphereGeometry> | THREE.SphereGeometry |
<meshStandardMaterial> | THREE.MeshStandardMaterial |
<ambientLight> | THREE.AmbientLight |
<directionalLight> | THREE.DirectionalLight |
<perspectiveCamera> | THREE.PerspectiveCamera |
<points> / <line> / <sprite> | THREE.Points 等 |
<color> | THREE.Color(attach 用) |
<fog> / <fogExp2> | THREE.Fog 等 |
<texture> | THREE.Texture |
<axesHelper> / <gridHelper> | THREE.AxesHelper 等 |
args: コンストラクタ引数
クラスのコンストラクタに渡す配列。
new BoxGeometry(2, 1, 1) は <boxGeometry args={[2, 1, 1]} />。
<sphereGeometry args={[1, 32, 32]} /> {/* radius, widthSeg, heightSeg */}
<meshStandardMaterial args={[{ color: "orange", roughness: 0.4 }]} />
<ambientLight args={[0xffffff, 0.6]} /> {/* color, intensity */}
args を変えるとオブジェクトが作り直される
args はコンストラクタ引数なので、変更すると R3F は古いインスタンスを破棄して
新しく作り直す(reconciler の recreate)。毎フレーム args を変えてはいけない。
動的に変えたい値は radius 等の prop か、geometry.parameters 直アクセスで。
props: インスタンスのプロパティ
JSX prop はインスタンスのフィールド代入に変換される。
position={[1,2,3]} は mesh.position.set(1,2,3) と同等。
<mesh
position={[0, 1, 0]}
rotation={[0, Math.PI / 4, 0]}
scale={[1.5, 1.5, 1.5]}
castShadow
receiveShadow
visible
frustumCulled
renderOrder={0}
userData={{ id: "hero" }}
>
<boxGeometry />
<meshStandardMaterial color="#9bd1ff" />
</mesh>
スカラの単一値は scale={1.5} のようにも書ける(内部で set(1.5,1.5,1.5))。
色も同様で文字列・16進・THREE.Color インスタンスのいずれも受け付ける。
ハイフン記法でネストプロパティに代入
shadow.mapSize.width = 2048 のようなネストしたフィールドには、
ハイフン区切りで一発代入できる。これは R3F の便利記法。
<directionalLight
position={[3, 5, 4]}
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
shadow-camera-far={20}
shadow-camera-left={-10}
shadow-camera-right={10}
shadow-bias={-0.0005}
/>
マテリアルでも便利:
<meshStandardMaterial
color="#7dd3fc"
map-anisotropy={4}
emissiveIntensity={0.5}
/>
子要素と attach
<mesh> の中の <boxGeometry /> や <meshStandardMaterial /> は
scene.add() されない。親オブジェクトの geometry / material プロパティに代入される。
これが attach で、R3F が型から自動推論する。
自動 attach の対応
| 子の型 | attach 先 |
|---|---|
BufferGeometry | parent.geometry |
Material | parent.material |
Texture | 明示的に指定(attach="map" 等) |
Fog | scene.fog(<fog attach="fog" />) |
Color | 明示的に(<color attach="background" />) |
明示的に attach
<mesh>
<boxGeometry />
<meshStandardMaterial>
<texture attach="map" args={[image]} />
<texture attach="normalMap" args={[normalImage]} />
</meshStandardMaterial>
</mesh>
配列 attach(マルチマテリアル)
Mesh.material が配列の時(多面体ごとに異なるマテリアル)の書き方:
<mesh>
<boxGeometry />
<meshStandardMaterial attach="material-0" color="red" />
<meshStandardMaterial attach="material-1" color="green" />
<meshStandardMaterial attach="material-2" color="blue" />
<meshStandardMaterial attach="material-3" color="yellow" />
<meshStandardMaterial attach="material-4" color="cyan" />
<meshStandardMaterial attach="material-5" color="magenta" />
</mesh>
ref で Three.js インスタンスに触る
ふつうの React の ref がそのまま使える。ref.current は Three.js のオブジェクトそのもの。
function Spin() {
const ref = useRef<THREE.Mesh>(null!)
useFrame((_, dt) => {
ref.current.rotation.y += dt
})
return (
<mesh ref={ref}>
<torusGeometry />
<meshStandardMaterial color="hotpink" />
</mesh>
)
}
callback ref パターン
<mesh ref={(mesh) => {
if (!mesh) return
mesh.geometry.computeBoundingSphere()
}} />
forwardRef で参照を子に渡す
const Box = forwardRef<THREE.Mesh, MeshProps>((props, ref) => (
<mesh ref={ref} {...props}>
<boxGeometry />
<meshStandardMaterial color="orange" />
</mesh>
))
<primitive> で既存インスタンスを差し込む
new THREE.Mesh() したインスタンスや、GLTF の scene を
そのままシーンに追加する差し込み口。
const gltf = useLoader(GLTFLoader, "/model.glb")
return <primitive object={gltf.scene} position={[0, 0, 0]} />
ジオメトリやマテリアルをマウントする時は attach も組み合わせ:
// 共有マテリアルを attach
<mesh>
<boxGeometry />
<primitive object={sharedMaterial} attach="material" />
</mesh>
primitive object を複数置くと壊れる
Three.js のオブジェクトは1つの親にしか属せない。同じ scene を2箇所に <primitive> すると、
後者が前者を奪う形になりおかしくなる。drei の <Clone> または scene.clone() を使う。
カメラを差し替える
Canvas の camera prop で初期化するのが手軽。
固定カメラを別途用意するなら、コンポーネントを宣言して makeDefault を付ける。
import { PerspectiveCamera, OrthographicCamera } from "@react-three/drei"
<Canvas>
<PerspectiveCamera makeDefault position={[3, 2, 5]} fov={50} />
...
</Canvas>
makeDefault は「これをデフォルトカメラとして使う」フラグ。複数のカメラに切り替える時、
makeDefault を切り替えるだけで OK。
サイズと viewport
Canvas は親要素のサイズを ResizeObserver で追跡する。親要素に高さが必要。
親が height: 0 だと canvas も 0 になり、何も見えない。
state.size= canvas のピクセルサイズ{ width, height, top, left }state.viewport= ワールド単位のサイズ(カメラから見た画面の物理的な大きさ)
/* 100vh の中で Canvas を全面に */
html, body, #root { margin: 0; height: 100%; }
部分的に Canvas を出すなら、親に明示的な高さを:
<div style={{ width: "100%", height: 400 }}>
<Canvas> ... </Canvas>
</div>
背景
透明にしたい
<Canvas gl={{ alpha: true }} style={{ background: "transparent" }}> ... </Canvas>
クリアカラー
<Canvas onCreated={({ gl }) => gl.setClearColor("#0b0d12")}> ... </Canvas>
scene.background に color / texture
<Canvas>
<color attach="background" args={["#0b0d12"]} />
...
</Canvas>
HDR 環境光と背景を一緒に
import { Environment } from "@react-three/drei"
<Canvas>
<Environment preset="sunset" background />
...
</Canvas>
カラースペースと flat / linear
Three.js r152+ から sRGB がデフォルトの色管理。R3F もこれに従う。
意図的に旧来の挙動が必要なら linear + flat:
<Canvas linear flat> ... </Canvas>
{/* linear: linear color space(既定は sRGB) */}
{/* flat: tone mapping を無効化(既定は ACES Filmic) */}
テクスチャ単位での設定はThree.js テクスチャを参照。 基本: カラー画像は sRGB、normal/roughness/metalness は NoColorSpace。
extend: 自分でクラスを追加
Three.js の examples に入っているクラス(OrbitControls 等)や、自作 shader 材質は
デフォルトの JSX には居ない。extend で登録すれば JSX で書けるようになる。
import { extend, useThree } from "@react-three/fiber"
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
extend({ OrbitControls })
function Controls() {
const { camera, gl } = useThree()
return <orbitControls args={[camera, gl.domElement]} />
}
とはいえこの登録は大半が drei に揃っている。素の OrbitControls を自前で
extend する代わりに import { OrbitControls } from "@react-three/drei" が早い。
自作 ShaderMaterial を JSX に
drei の shaderMaterial ヘルパーを使うと、自作 shader を JSX として登録できる。シェーダのページ参照。
イベントの土台
Canvas はマウント時にイベントシステムを立ち上げる。onClick や onPointerOver を
メッシュに直接書けるのはこの仕組みのおかげ。詳しくは インタラクション。
onPointerMissed
Canvas の prop として渡すと、シーン上どのオブジェクトにも当たらなかった時のクリックを取れる。 「外をタップで選択解除」の典型実装に。
<Canvas onPointerMissed={() => setSelected(null)}> ... </Canvas>
複数の Canvas / 重ねる
ページ内に複数の Canvas を置いてもよい(リスト UI に小さい 3D を散りばめる等)。 各 Canvas は独立した WebGLRenderer を持つのでGPU メモリは増える点に注意。
透明 Canvas をオーバーレイ
<div style={{ position: "relative" }}>
<img src="hero.jpg" />
<Canvas
style={{ position: "absolute", inset: 0, pointerEvents: "none" }}
gl={{ alpha: true }}
>
{/* パーティクル等の装飾レイヤー */}
</Canvas>
</div>
frameloop の選び方
"always"(既定) — 毎フレーム描画。ゲームや動的シーンに。"demand"— 必要時だけ。静的シーンで CPU/GPU の節約に絶大。パフォーマンス参照。"never"— 自動描画しない。完全に手動制御。
Stats / Performance Monitor
FPS や draw call を視覚化したい時は drei の <Stats /> または r3f-perf。
import { Stats } from "@react-three/drei"
<Canvas>
<Stats />
...
</Canvas>
典型的な Provider 構成
実用アプリでは Canvas を Suspense / Theme / 状態管理プロバイダで包む形になりがち:
<Suspense fallback={<Loading />}>
<Canvas
shadows
dpr={[1, 2]}
camera={{ position: [3, 2, 5], fov: 50 }}
>
<ambientLight intensity={0.4} />
<directionalLight castShadow position={[3, 5, 4]} />
<Environment preset="city" />
<OrbitControls enableDamping />
<Scene />
</Canvas>
</Suspense>
外に置くと「Canvas を描かずローダーが見える」、中(Canvas 直下)に置くと
「3D シーンの中だけにフォールバックを出して周りの DOM は表示」が選べる。
実用では Canvas の中の方が使い勝手が良いケースが多い。