基本構成
3D アプリの土台。4つの主要オブジェクト(Renderer / Scene / Camera / Mesh)と レンダリングループを正しく組み立てれば、あとは枝葉を生やしていくだけで何でも作れる。
4つの主要オブジェクト
WebGLRenderer
ブラウザの WebGL(または最近は WebGPU)と話す描画エンジン。canvas 要素を内部で
作り、renderer.render(scene, camera) でその時点のシーンを 1 フレーム描く。
const renderer = new THREE.WebGLRenderer({
antialias: true, // ジャギーを抑える(軽い MSAA)
alpha: false, // 背景透明にしたい時は true
powerPreference: "high-performance",
})
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
document.body.appendChild(renderer.domElement)
setPixelRatioは2 で打ち止めするのが定石。Retina で 3 にすると 9 倍の負荷。setSize(w, h, false)の第3引数falseで CSS サイズ更新を抑制できる(自前で CSS を当てている時)。- 影を出したい時は
renderer.shadowMap.enabled = true。詳しくは ライトと影。 - トーンマッピング:
renderer.toneMapping = THREE.ACESFilmicToneMapping+renderer.toneMappingExposure = 1.0。
Scene
シーングラフのルート。Object3D のツリーが scene.add() でぶら下がる。
const scene = new THREE.Scene()
scene.background = new THREE.Color("#0b0d12") // 単色背景
// scene.fog = new THREE.Fog("#0b0d12", 5, 30) // 距離フォグ
scene.backgroundはColor/Texture/CubeTextureを入れられる。scene.environmentに HDR を入れると、Standard / Physical Material が IBL で照らされる。scene.fogで距離フォグ、FogExp2で指数フォグ。
PerspectiveCamera
現実の遠近感を再現するカメラ。FOV(視野角)が大きいほど広角に、小さいほど望遠に。
const camera = new THREE.PerspectiveCamera(
50, // fov(度)
window.innerWidth / window.innerHeight, // aspect
0.1, // near
100, // far
)
camera.position.set(3, 2, 5)
camera.lookAt(0, 0, 0)
- near / far の範囲外は描画されない。near は 0 にしない(z-fighting と精度劣化)。
far / nearの比が大きいほど深度バッファの精度が落ちる。必要最小限のレンジに。- 並行投影が欲しい時は
OrthographicCamera(カメラ参照)。
Mesh
形(geometry)+ 見た目(material)を組み合わせた、画面に見えるオブジェクトの基本単位。
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: "orange", roughness: 0.4 }),
)
mesh.position.set(0, 0, 0)
mesh.rotation.set(0, Math.PI / 4, 0)
mesh.castShadow = true
mesh.receiveShadow = true
scene.add(mesh)
レンダリングループ
毎フレーム描画したいなら setAnimationLoop にコールバックを渡す。
requestAnimationFrame を直接書くよりWebXR 対応が楽。
renderer.setAnimationLoop(() => {
mesh.rotation.y += 0.01
renderer.render(scene, camera)
})
止めるには renderer.setAnimationLoop(null)。
静的シーン(変化がない場合)は1回だけ render すれば十分。
delta を使う(フレームレート非依存)
position.x += 0.01 のような固定値は 30fps と 120fps で速度が4倍違う。
THREE.Clock で前フレームからの経過秒数を取る。
const clock = new THREE.Clock()
renderer.setAnimationLoop(() => {
const dt = clock.getDelta() // 前フレームからの秒数
const t = clock.getElapsedTime() // 開始からの累計秒数
mesh.rotation.y += dt * 1.0 // 1秒で 1 ラジアン
mesh.position.y = Math.sin(t * 2) // 累計時間で振動
renderer.render(scene, camera)
})
Object3D ヒエラルキー
Mesh / Light / Camera / Group など、シーンに乗るものはほぼ全部 Object3D の派生。
共通 API(position / rotation / scale / add / remove / traverse)はここで定義されている。
| API | 意味 |
|---|---|
position | Vector3。座標。position.set(x,y,z)。 |
rotation | Euler。XYZオーダーが既定。rotation.y = Math.PI。 |
quaternion | Quaternion。回転を直接持つ表現(補間が安全)。 |
scale | Vector3。大きさ。scale.setScalar(2) で全方向2倍。 |
visible | boolean。子も含めて非表示にできる。 |
matrix / matrixWorld | 変換行列。普通は触らない。 |
add(...children) / remove(child) | 子要素の出し入れ。 |
traverse(cb) | 自分含む全子孫を巡回。 |
getWorldPosition(target) | ワールド座標を target に書き出す。 |
lookAt(x, y, z) | 指定点を向くよう quaternion を更新。 |
Group でまとめる
論理的に「単位」になるオブジェクトを Group(= 中身のない Object3D)にまとめると、
位置・回転・スケールを一括で動かせる。
const robot = new THREE.Group()
robot.add(body, head, leftArm, rightArm)
robot.position.set(0, 0, 0)
robot.rotation.y = Math.PI / 4
scene.add(robot)
リサイズ対応
ウィンドウサイズが変わったらカメラの aspect とレンダラの setSize を更新する。
これを忘れると画面が伸びる。
window.addEventListener("resize", () => {
const w = window.innerWidth, h = window.innerHeight
camera.aspect = w / h
camera.updateProjectionMatrix()
renderer.setSize(w, h)
})
カラー設定(重要)
Three.js r152 以降はデフォルトで sRGB 色管理。古いチュートリアルどおりに書くと色が薄く/濃く見えることがある。
- renderer の output:
renderer.outputColorSpace = THREE.SRGBColorSpace(既定)。 - テクスチャ:
texture.colorSpace = THREE.SRGBColorSpace(カラー画像)orNoColorSpace(normal map / data 系)。 - 素朴な見た目に戻したい時のみ
renderer.toneMapping = THREE.NoToneMapping。
クリーンアップ
Three.js は明示的に dispose() しないとメモリが残る。
ページ遷移や SPA で破棄するとき:
function disposeAll(root) {
root.traverse((o) => {
if (o.isMesh) {
o.geometry?.dispose()
const mats = Array.isArray(o.material) ? o.material : [o.material]
for (const m of mats) {
for (const k of Object.keys(m)) {
const v = m[k]
if (v && v.isTexture) v.dispose()
}
m.dispose()
}
}
})
renderer.dispose()
renderer.domElement.parentNode?.removeChild(renderer.domElement)
}
詳細は 注意点・パフォーマンス。