シーン構築
R3F は薄いラッパなので、シーン作りは Three.js そのものの知識が直に効く。 ライト・マテリアル・ジオメトリ・カメラを R3F の流儀で書くとどうなるかをまとめる。
シーンの最低限
オブジェクトを「見せる」には少なくとも カメラ + ライト + マテリアルが必要。
MeshStandardMaterial や MeshPhysicalMaterial はライトがないと真っ黒。
<Canvas camera={{ position: [3, 2, 5], fov: 50 }} shadows>
<color attach="background" args={["#0b0d12"]} />
<ambientLight intensity={0.4} />
<directionalLight position={[3, 5, 4]} intensity={1.2} castShadow />
<mesh castShadow receiveShadow>
<boxGeometry />
<meshStandardMaterial color="#7dd3fc" />
</mesh>
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]} receiveShadow>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial color="#1f2430" />
</mesh>
</Canvas>
<color attach="background" /> はシーンの background プロパティに THREE.Color を attach する書き方。
ライト
| JSX | 用途 | 影 |
|---|---|---|
<ambientLight> | 全方向から一様。陰影なし。底上げ用。 | 不可 |
<hemisphereLight> | 上半球と下半球で別の色。屋外擬似 GI。 | 不可 |
<directionalLight> | 並行光。太陽光。 | 可 |
<pointLight> | 点光源。電球。距離減衰あり。 | 可 |
<spotLight> | スポット。angle/penumbra。 | 可 |
<rectAreaLight> | 矩形面光源。Standard/Physical のみ反応。 | 不可 |
3点照明の典型
<>
<ambientLight intensity={0.3} />
<directionalLight position={[5, 5, 5]} intensity={1.5} castShadow /> {/* キー */}
<directionalLight position={[-5, 3, -3]} intensity={0.8} color="#aaccff" /> {/* フィル */}
<directionalLight position={[0, 4, -8]} intensity={1.2} color="#ffaaaa" /> {/* リム */}
</>
環境光(IBL)を入れると一気にリアルになる。drei の <Environment preset="city" /> で
1行で導入できる。Standard / Physical Material は環境光に反応する。
影
影を出すには3点セットが必要:
<Canvas shadows>で shadowmap 有効化- 影を落とす光源(
directionalLight/spotLight/pointLight)にcastShadow - 影を落とすメッシュに
castShadow、影を受けるメッシュにreceiveShadow
<directionalLight
position={[3, 5, 4]}
intensity={1.2}
castShadow
shadow-mapSize-width={2048}
shadow-mapSize-height={2048}
shadow-camera-far={20}
shadow-camera-left={-5}
shadow-camera-right={5}
shadow-camera-top={5}
shadow-camera-bottom={-5}
shadow-bias={-0.0005}
shadow-normalBias={0.02}
/>
shadow camera を可視化
範囲が合っていないと影が切れる。デバッグ時はヘルパーで可視化:
import { useHelper } from "@react-three/drei"
import { CameraHelper } from "three"
function Sun() {
const ref = useRef<THREE.DirectionalLight>(null!)
useHelper(ref.current?.shadow && { current: ref.current.shadow.camera }, CameraHelper)
return <directionalLight ref={ref} castShadow position={[5, 10, 5]} />
}
静的シーンは影を「焼く」
動かないライト・メッシュなら1回計算して固定:
import { BakeShadows } from "@react-three/drei"
<Canvas shadows>
<BakeShadows />
...
</Canvas>
柔らかい接地影
全シャドウを焼けない時、床への影だけを綺麗に出すなら drei の <ContactShadows />:
import { ContactShadows } from "@react-three/drei"
<ContactShadows
position={[0, -0.5, 0]}
opacity={0.5}
scale={10}
blur={2.5}
far={4}
/>
ジオメトリ
Three.js の組み込みジオメトリは全部使える。よく使うのは:
| JSX | 主な引数 |
|---|---|
<boxGeometry args={[w, h, d]} /> | 幅・高さ・奥行き |
<sphereGeometry args={[r, ws, hs]} /> | 半径・縦横セグメント |
<planeGeometry args={[w, h]} /> | 平面 |
<cylinderGeometry args={[topR, bottomR, h, segs]} /> | 円柱 |
<coneGeometry args={[r, h, segs]} /> | 円錐 |
<torusGeometry args={[r, tube, radSeg, tubSeg]} /> | ドーナツ |
<torusKnotGeometry /> | 結び目 |
<icosahedronGeometry args={[r, detail]} /> | 正二十面体(球の近似に) |
<capsuleGeometry args={[r, length, capSeg, radSeg]} /> | カプセル |
<tubeGeometry args={[curve, segs, r, radSeg]} /> | カーブに沿ったチューブ |
<extrudeGeometry args={[shape, opts]} /> | 2D Shape の押し出し |
BufferGeometry を自前で組む
function Triangle() {
const positions = useMemo(() => new Float32Array([
0, 1, 0,
-1, -1, 0,
1, -1, 0,
]), [])
return (
<mesh>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
args={[positions, 3]}
/>
</bufferGeometry>
<meshBasicMaterial color="lime" wireframe />
</mesh>
)
}
マテリアル
| JSX | 性質 |
|---|---|
<meshBasicMaterial> | ライト無視。常に同じ明るさ。HUD向き。 |
<meshLambertMaterial> | 軽い拡散反射。古典的。 |
<meshPhongMaterial> | 古典 PBR 風。 |
<meshStandardMaterial> | 定番 PBR。roughness/metalness。 |
<meshPhysicalMaterial> | Standard を拡張。clearcoat/transmission(ガラス)。 |
<meshToonMaterial> | セルシェーディング。 |
<meshNormalMaterial> | 面法線で着色(デバッグ)。 |
<meshMatcapMaterial> | matcap テクスチャでライト不要のリッチ表現。 |
<shaderMaterial> | 自作 GLSL(シェーダ参照)。 |
テクスチャを当てる
drei の useTexture が一番楽。useLoader(TextureLoader, ...) でも同じ結果。
import { useTexture } from "@react-three/drei"
function Wall() {
const props = useTexture({
map: "/wall_color.jpg",
normalMap: "/wall_normal.jpg",
roughnessMap: "/wall_rough.jpg",
aoMap: "/wall_ao.jpg",
})
return (
<mesh>
<planeGeometry args={[3, 3]} />
<meshStandardMaterial {...props} />
</mesh>
)
}
マテリアルの動的調整
function Glass() {
return (
<mesh>
<sphereGeometry args={[1, 64, 64]} />
<meshPhysicalMaterial
transmission={1}
thickness={0.5}
roughness={0.05}
ior={1.5}
clearcoat={1}
clearcoatRoughness={0}
/>
</mesh>
)
}
カメラ
Canvas の camera prop で済むなら一番楽。明示的に置きたい時は drei の
<PerspectiveCamera makeDefault /> がきれい。
カメラを動かす
- マウスでぐるぐる: drei の
<OrbitControls /> - FPS風:
<PointerLockControls />+ 自前の移動 - カメラを追従:
useFrame内でcamera.position.lerp(target, 0.1) - シネマティック:
<CameraControls />(drei)
追従カメラの典型パターン
function ChaseCam({ target }: { target: THREE.Object3D }) {
const tmp = useRef(new THREE.Vector3()).current
useFrame((state) => {
target.getWorldPosition(tmp)
state.camera.position.lerp(tmp.clone().add(new THREE.Vector3(0, 2, 5)), 0.1)
state.camera.lookAt(tmp)
})
return null
}
グルーピング
<group> は THREE.Group。位置・回転・スケールを子ごと変換する箱。
function Robot() {
return (
<group position={[0, 0, 0]} rotation={[0, Math.PI / 4, 0]}>
<Body />
<Head position={[0, 1, 0]} />
<Arm position={[0.5, 0.5, 0]} />
<Arm position={[-0.5, 0.5, 0]} />
</group>
)
}
<primitive> で既存の Three.js オブジェクトを差し込む
const gltf = useLoader(GLTFLoader, "/model.glb")
return <primitive object={gltf.scene} position={[0, 0, 0]} />
同じインスタンスを複数置きたい時は drei の <Clone>:
import { Clone } from "@react-three/drei"
const { scene } = useGLTF("/tree.glb")
<>
<Clone object={scene} position={[0, 0, 0]} />
<Clone object={scene} position={[3, 0, 0]} />
<Clone object={scene} position={[6, 0, 0]} />
</>
背景 / 環境
- 単色:
<color attach="background" args={["#000"]} /> - グラデーション/星空: drei の
<Sky />/<Stars />/<Cloud /> - HDR 環境光(IBL):
<Environment preset="city" />など。backgroundを渡すと背景にも反映。
import { Environment, Sky } from "@react-three/drei"
<Environment preset="sunset" background />
{/* または */}
<Sky sunPosition={[100, 20, 100]} />
Fog(霧)
{/* 線形フォグ */}
<fog attach="fog" args={["#0b0d12", 5, 30]} />
{/* 指数フォグ */}
<fogExp2 attach="fog" args={["#0b0d12", 0.05]} />
シーンの探索 / traverse
ロードされた GLTF の中身を一括加工する時:
function Tweaked() {
const { scene } = useGLTF("/model.glb")
useEffect(() => {
scene.traverse((o) => {
if (o.isMesh) {
o.castShadow = true
o.receiveShadow = true
o.material.envMapIntensity = 1.5
}
})
}, [scene])
return <primitive object={scene} />
}
ヘルパー
デバッグ可視化。drei の useHelper が便利。
import { useHelper } from "@react-three/drei"
import { BoxHelper } from "three"
function Box() {
const ref = useRef<THREE.Mesh>(null!)
useHelper(ref, BoxHelper, "yellow")
return <mesh ref={ref}> ... </mesh>
}
軸とグリッド:
<axesHelper args={[3]} /> {/* X赤 Y緑 Z青 */}
<gridHelper args={[10, 10]} />
シーン全体に効く drei コンポーネント
<Stage />— ライト + 環境光 + 影をひとまとめにした「商品撮影スタジオ」<Center />— 子要素のバウンディングから中央寄せ<Bounds />— 子全体が画面に収まるようカメラ調整<Resize />— 子要素を一定サイズに正規化<Float />— ふわふわ浮遊
<Stage /> の例
import { Stage } from "@react-three/drei"
<Canvas shadows>
<Stage environment="city" intensity={0.5} adjustCamera shadows="contact">
<MyModel />
</Stage>
</Canvas>
これだけでライティング・接地影・カメラ調整が全部入る。プロトタイプに最強。