ライトと影

Standard / Physical / Lambert / Phong / Toon Material はライトがないと真っ黒。 逆にライトを当てる位置・強さで雰囲気はガラリと変わる。影は別途3点セットを揃えると出る。

ライト一覧

ライト性質
AmbientLight全方向から一様。陰影なし。底上げ専用。不可
HemisphereLight上半球と下半球で色違い。屋外の擬似 GI。不可
DirectionalLight並行光。太陽光。可(よく使う)
PointLight点光源。電球。距離減衰あり。
SpotLight円錐状。angle / penumbra
RectAreaLight矩形の面光源。Standard/Physical のみ反応。影は不可。不可

基本セット(3点照明)

悩んだら以下の3点を入れておく。ambient + directional + 環境光でだいたいまとまる。

// 全体の底上げ
scene.add(new THREE.AmbientLight(0xffffff, 0.4))

// 太陽光相当
const dl = new THREE.DirectionalLight(0xffffff, 1.2)
dl.position.set(3, 5, 4)
scene.add(dl)

// 環境光(IBL)— Standard/Physical を一気にリッチに
const pmrem = new THREE.PMREMGenerator(renderer)
const env = pmrem.fromScene(new THREE.Scene()).texture // 自前の単色scene
scene.environment = env

環境光は HDR ファイルから作るのが一般的。テクスチャローダーに詳しく書く。

各ライトの詳細

AmbientLight

new THREE.AmbientLight(color = 0xffffff, intensity = 1)

全方向から均一に光が当たる。シャドウは出ない。暗部を持ち上げる用に弱めに入れるのが基本。

HemisphereLight

new THREE.HemisphereLight(skyColor, groundColor, intensity)

屋外で「空が青、地面が茶」みたいな擬似 GI を簡単に。Ambient の代わりに使うと一気に雰囲気が出る。

DirectionalLight

const dl = new THREE.DirectionalLight(0xffffff, 1.2)
dl.position.set(3, 5, 4)
dl.target.position.set(0, 0, 0)  // どこを照らすか
scene.add(dl)
scene.add(dl.target)              // target もシーンに add 必要

PointLight

const pl = new THREE.PointLight(0xffaa55, 2, 10, 2)
//                                color   intensity dist  decay
pl.position.set(0, 2, 0)
scene.add(pl)

SpotLight

const sl = new THREE.SpotLight(0xffffff, 5, 20, Math.PI / 6, 0.3, 2)
//                              color  intensity dist  angle  penumbra decay
sl.position.set(2, 5, 2)
sl.target.position.set(0, 0, 0)
scene.add(sl, sl.target)

RectAreaLight

import { RectAreaLightUniformsLib } from "three/addons/lights/RectAreaLightUniformsLib.js"
RectAreaLightUniformsLib.init() // 初回1回呼ぶ

const ral = new THREE.RectAreaLight(0xffffff, 5, 4, 2)
ral.position.set(0, 5, 0)
ral.lookAt(0, 0, 0)
scene.add(ral)

矩形の面光源。Standard / Physical でしか反応しない。 影は出せない。スタジオ照明っぽい雰囲気に強い。

ライトヘルパー

ライトの位置や向きを視覚化するヘルパーがある。デバッグに必須。

scene.add(new THREE.DirectionalLightHelper(dl, 1))
scene.add(new THREE.PointLightHelper(pl, 0.2))
scene.add(new THREE.SpotLightHelper(sl))
scene.add(new THREE.HemisphereLightHelper(hl, 1))

// Shadow camera のフラスタム可視化
scene.add(new THREE.CameraHelper(dl.shadow.camera))

影(シャドウマップ)

Three.js の影はシャドウマップ方式。光源視点から「深度マップ」を撮って、レンダリング時に 各点が遮蔽されているか判定する。3点セットを必ず揃える。

Step 1: レンダラを有効化

renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
// 候補:
// THREE.BasicShadowMap         — 高速・カクカク
// THREE.PCFShadowMap           — 既定。ふつう
// THREE.PCFSoftShadowMap       — 柔らかい
// THREE.VSMShadowMap           — 別方式(要対応)

Step 2: 光源側

dl.castShadow = true
dl.shadow.mapSize.set(2048, 2048)
dl.shadow.camera.near = 0.5
dl.shadow.camera.far  = 30
dl.shadow.camera.left   = -10
dl.shadow.camera.right  =  10
dl.shadow.camera.top    =  10
dl.shadow.camera.bottom = -10
dl.shadow.bias = -0.0005       // shadow acne 対策

Step 3: メッシュ側

obj.castShadow    = true   // この物体が影を「落とす」
floor.receiveShadow = true // この物体が影を「受ける」

3つどれかが欠けると影は出ない。よくあるバグなのでチェックリスト化しておくと楽。

影のクオリティと負荷

静的シーンでは焼き付ける

renderer.shadowMap.autoUpdate = false
renderer.shadowMap.needsUpdate = true
// 1回レンダリングしたあとは、影は固定される
renderer.render(scene, camera)
renderer.shadowMap.needsUpdate = false

環境光(IBL)

Standard / Physical を一気にリアルにするには、HDR の環境光scene.environment に当てる。

import { RGBELoader } from "three/addons/loaders/RGBELoader.js"

new RGBELoader().load("/hdr/studio.hdr", (hdr) => {
  hdr.mapping = THREE.EquirectangularReflectionMapping
  scene.environment = hdr
  scene.background  = hdr  // 背景にも使う場合
})

個別マテリアルだけに反映したい時は material.envMap = hdr

典型的なライティング例

商品紹介(スタジオ風)

屋外(昼)

夜・暗いシーン

Light は「足し算」

Three.js のライトは純粋に加算される。intensity を雑に上げるとサチる(白飛び)。 全体の調整は renderer.toneMappingExposure でやるのが綺麗。