基本(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>
  )
}

これだけで:

<Canvas> の主な props

prop役割
cameraobjectデフォルトカメラの初期化オプション
orthographicbooleantrue で OrthographicCamera に切替
globject | WebGLRenderer | (canvas) => rendererWebGLRenderer のオプション or 自前生成
shadowsboolean | "soft" | "basic" | "percentage" | "variance"シャドウマップタイプ
dprnumber | [min, max]デバイスピクセル比。[1, 2] で clamp が定石
flatbooleantrue で tone mapping を無効化
linearbooleantrue で linear color space
frameloop"always" | "demand" | "never"レンダーループの挙動
onCreated(state) => void初期化完了直後のコールバック
onPointerMissed(e) => voidシーン内何もない場所のクリック
eventsEventManagerイベントシステムの差し替え
raycasterRaycasterParametersraycaster オプション
sceneScenePropsscene.background
resizeResizeOptionsリサイズ動作の調整
performance{ current, min, max, debounce }性能ターゲット(regress 機構)
legacyboolean旧来のカラースペース挙動

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 先
BufferGeometryparent.geometry
Materialparent.material
Texture明示的に指定(attach="map" 等)
Fogscene.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.currentThree.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 になり、何も見えない。

よくある親要素
/* 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 はマウント時にイベントシステムを立ち上げる。onClickonPointerOver を メッシュに直接書けるのはこの仕組みのおかげ。詳しくは インタラクション

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 の選び方

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>
Suspense の境界は Canvas の中でも外でもいい

に置くと「Canvas を描かずローダーが見える」、(Canvas 直下)に置くと 「3D シーンの中だけにフォールバックを出して周りの DOM は表示」が選べる。
実用では Canvas の中の方が使い勝手が良いケースが多い。