Three.jsのレンダラー・ライト・カメラの使い方と設定まとめ
前回はライトを無視する MeshBasicMaterial でキューブを表示しました。今回はちゃんとライトを当てて、影を落として、マウスでカメラを回せるようにします。地味な回に見えますが、レンダラー・ライト・カメラは何を作っても必ず触る部分なので、ここの理解が後々効いてきます。
今回のデモは右上のGUIでライトの強さと視野角をいじれるようにしました。数値を動かすと見え方がどう変わるか、実際に触ってみるのが一番早いです。
WebGLRendererのオプション
レンダラーを作るときのオプションでよく使うのはこのあたりです。
const renderer = new THREE.WebGLRenderer({
antialias: true, // ギザギザ(ジャギー)を軽減する
alpha: true, // 背景を透過させる(HTMLと重ねるときに使う)
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 影を使うなら明示的にON
antialias: true はほぼ必須です。デフォルトはoffで、offのままだとオブジェクトの輪郭がギザギザになります。
alpha: true は背景を透明にするオプションで、普段は不要ですが、Three.jsのcanvasを他のHTML要素の上に重ねる構成で必要になります。この使い方はCSS3DRendererとの共存の回で詳しくやります。
あと高解像度ディスプレイで表示がぼやける場合は renderer.setPixelRatio(window.devicePixelRatio) を入れると直ります。ただしピクセル数が増えるぶん負荷も上がるので、モバイルも考えるなら上限を2に制限するのが定石です。
ライトは2種類の組み合わせから始める
Three.jsには何種類もライトがありますが、まずはこの2つの組み合わせで十分です。
// 環境光: 方向を持たず、シーン全体を均一に照らす
const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);
// 平行光源: 太陽光のように一定方向から照らす
const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
directionalLight.position.set(5, 8, 3);
scene.add(directionalLight);
役割分担ははっきりしていて、**環境光は「真っ黒な影を作らないための底上げ」、平行光源は「立体感を出すためのメイン光源」**です。
デモで環境光を0にしてみると、光が当たっていない面が完全な黒になって不気味になります。逆に環境光だけにすると、全部の面が同じ明るさになってのっぺりします。環境光を弱め(0.3前後)+平行光源をメインに、というバランスが基本形です。
平行光源の position は「光が来る方向」を決めるためのもので、遠さは関係ありません。(5, 8, 3) でも (500, 800, 300) でも同じ向きなら結果は同じです。
似たものに HemisphereLight(空の色と地面の色の2色で照らす)もあって、屋外シーンだとこちらの方が自然になることが多いです。環境光の代わりに使えます。
影の設定は3ヶ所
影はデフォルトでは一切出ません。「レンダラー・ライト・オブジェクト」の3ヶ所で明示的にONにする必要があります。
renderer.shadowMap.enabled = true; // 1. レンダラー
directionalLight.castShadow = true; // 2. ライト(影を作る)
box.castShadow = true; // 3. 影を落とす側のオブジェクト
floor.receiveShadow = true; // 影を受ける側のオブジェクト
どれか1つでも忘れると影が出ないので、「影が出ない!」となったらこの3ヶ所を順に確認してください。私は毎回どれかを忘れます。
PerspectiveCameraの4つの引数
const camera = new THREE.PerspectiveCamera(
70, // fov: 視野角(度)
window.innerWidth / window.innerHeight, // aspect: アスペクト比
0.1, // near: これより近くは描画しない
1000 // far: これより遠くは描画しない
);
fov(視野角)は画角の広さです。デモのGUIで動かすとわかりますが、小さくすると望遠レンズのように圧縮された画になり、大きくすると広角レンズのように遠近感が誇張されます。人の視界に近いのは60〜75くらいで、迷ったら70前後にしておけば無難です。
nearとfarは描画する距離の範囲です。この範囲外のオブジェクトは存在していても描画されません。「遠くに置いたオブジェクトが表示されない」というときは、だいたいfarが小さすぎるのが原因です。かといってnearを極端に小さく、farを極端に大きくすると、奥行きの精度が足りなくなって面がチラつくZファイティングという現象が起きるので、シーンの規模に合わせてほどほどに設定します。
OrbitControlsでカメラを操作する
マウスでぐりぐり視点を回せるようにするのがOrbitControlsです。公式アドオンなのでimportmapに three/addons/ を登録していればすぐ使えます。
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
controls.maxPolarAngle = Math.PI / 2.01; // 地面より下に回り込めなくする
controls.maxDistance = 30; // ズームアウトの上限
そのまま使うと、カメラが地面の下に潜れてしまって床の裏側が見えたり、無限にズームアウトできてシーンが米粒になったりします。maxPolarAngle と maxDistance の2つを設定しておくと操作感が一気にまともになります。
Math.PI / 2.01 という中途半端な値は「真横(90度)よりほんの少しだけ上まで」という意味です。ぴったり Math.PI / 2 にすると地面すれすれの視点になって床が線になるので、少しだけ余裕を持たせています。
似たコントロールに MapControls もあります。こちらは地図アプリのようにドラッグで平行移動するタイプで、俯瞰で見せたいシーンに向いています。
ライトの調整はGUIを使うと早い
ライトの強さや位置は数字をいじって保存してリロード…を繰り返すと日が暮れるので、Three.js公式に同梱されているlil-guiを使うのがおすすめです。
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
const gui = new GUI();
gui.add(ambientLight, 'intensity', 0, 3, 0.1).name('環境光の強さ');
gui.add(directionalLight, 'intensity', 0, 5, 0.1).name('平行光源の強さ');
gui.add(オブジェクト, 'プロパティ名', 最小, 最大, 刻み) だけでスライダーが生えます。開発中だけ有効にして、ちょうどいい値が見つかったらコードに書き写す、という使い方をしています。
まとめ
- レンダラーは
antialias: trueが基本、影を使うならshadowMap.enabledも - ライトは環境光(底上げ)+平行光源(立体感)の組み合わせから
- 影はレンダラー・ライト・オブジェクトの3ヶ所すべてでONにする
- カメラのnear/farはシーンの規模に合わせる、fovは70前後が無難
- OrbitControlsは
maxPolarAngleとmaxDistanceを設定して使う
Three.js解説シリーズ(全7回)