
[UIデザイナーが学ぶ]Three.jsの影の基本的な話
普段はメーカーで影の薄いUIデザイナーとして勤務しております。
あまり業務では利用しませんが、Three.jsの影について学び直しているので、備忘録的にまとめています。
影の表示手順
レンダラーで影を有効にする。
ライトで影を有効にする。
影を落とすMeshを設定する
影を受けるMesh設を設定する
// レンダラーで影を有効にする
const renderer = new THREE.WebGLRenderer();
renderer.shadowMap.enabled = true;
// ライトで影を有効にする
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.castShadow = true;
scene.add(directionalLight);
// 影を落とすMeshを設定する
const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.5, 32, 32), material);
sphere.castShadow = true; // 影をキャストする
// 影を受けるMesh設を設定する
const plane = new THREE.Mesh(new THREE.PlaneGeometry(5, 5), material);
plane.rotation.x = -Math.PI * 0.5;
plane.position.y = -0.5;
plane.receiveShadow = true; // 影を受ける
scene.add(sphere, plane);
影をサポートするライト
PointLight
DirectionalLight
SpotLight
ShadowMapのサイズを変更する
LightがshadowMapの情報を持っています。
// ライトで影を有効にする
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.castShadow = true;
scene.add(directionalLight);
console.log(directionalLight.shadow) // shadow情報を確認できる
shadowMapのサイズがwidth: 512、height:512となっているのが確認できました。

mapSizeを調整してみます。
1024を設定すると影がきれいになりました。
※サイズは2の累乗の値を設定してください
directionalLight.shadow.mapSize = new THREE.Vector2(1024, 1024);
// 以下の設定でもOK
// directionalLight.shadow.mapSize.width = 1024
// directionalLight.shadow.mapSize.height = 1024

shadowの中にあるcameraについて
以下のようにLightのshadow情報を取得するとcamreaの情報も保持しているのが確認できます。
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.castShadow = true;
console.log(directionalLight.shadow.camera)
// {
// animations: [],
// bottom: -5,
// castShadow: false
// children: []
// ...
カメラはなぜあるのか
簡単に言うと、shadow.camera は影を生成するためのライト専用のカメラです。
Three.jsでは、影を生成するためにライトの視点からシーンを見て、どのオブジェクトが影を落とすかを決定します。このとき、ライトがカメラのように振る舞います。このカメラの設定を行うのが shadow.camera です。
具体的には、shadow.camera はライトの視点からシーンをレンダリングし、その結果をシャドウマップとして保存します。このシャドウマップを使って、シーン内のオブジェクトに影を投影します。
shadow.cameraをHelperを利用して可視化する
THREE.CameraHelperを利用して可視化することができます。
const directionalLightCameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera)
scene.add(directionalLightCameraHelper)

camraなので、near、far、left、right、top、bottomなどでレンダリングする範囲を限定することができます。
以下は、farを小さい値にした場合です、影が途切れているのがわかります。

レンダリング範囲を小さくすることで、影の品質が向上します。
影をぼかす
radiusプロパティを利用すると、影のぼかし効果を調整することができます。このプロパティを設定することで、影のエッジを滑らかにすることができます。
ただし、radiusプロパティのぼかしは物体の距離によって変化しません。
物体が遠いところにあろうと、近いところにあろうと常に一定の表示となります。
directionalLight.shadow.radius = 5

シャドウマップの種類
shadowMap.type は、Three.js で使用されるシャドウマップの種類を指定するプロパティです。シャドウマップの種類によって、影の品質やパフォーマンスが異なります。以下に主要なシャドウマップの種類を説明します:
THREE.BasicShadowMap:
最も基本的なシャドウマップ。
パフォーマンスは高いが、影の品質は低い。
エッジがギザギザになることがある。
THREE.PCFShadowMap:
Percentage-Closer Filtering (PCF) を使用したシャドウマップ。
影のエッジが滑らかになる。
パフォーマンスと品質のバランスが良い。
THREE.PCFSoftShadowMap:
PCF をさらに改良したソフトシャドウマップ。
影のエッジがさらに滑らかで、柔らかい影を生成する。
パフォーマンスはやや低いが、影の品質は高い。
THREE.VSMShadowMap:
Variance Shadow Maps (VSM) を使用したシャドウマップ。
高品質な影を生成し、光の漏れを防ぐ。
パフォーマンスはやや低いが、特定のシーンで非常に効果的。

デフォルトはTHREE.PCFShadowMap。
品質を向上させたかったら、PCFSoftShadowMapを利用するのが良さそう。
だたし、PCFSoftShadowMapはradiusは利用できません。
影のパフォーマンスを考える
複数のライトを追加して影を作成する場合、パフォーマンスに影響を与える可能性があります。
シャドウマップの数:
各ライトが影を生成するためにシャドウマップを作成します。ライトの数が増えると、シャドウマップの数も増え、レンダリングの負荷が高くなります。
シャドウマップの解像度:
高解像度のシャドウマップは影の品質を向上させますが、メモリ使用量とレンダリング時間が増加します。必要に応じて解像度を調整してください。
シャドウマップの種類:
THREE.PCFSoftShadowMap や THREE.VSMShadowMap などの高品質なシャドウマップは、影の品質を向上させますが、パフォーマンスに影響を与える可能性があります。シーンに応じて適切なシャドウマップの種類を選択してください。
ライトの種類:
DirectionalLight、PointLight、SpotLight など、ライトの種類によって影の生成方法が異なります。特に PointLight や SpotLight は、影の生成に多くの計算リソースを必要とします。
改善策
◎影のみ(ベイクした)テクスチャを利用する。
影をテクスチャ画像として用意します。
少し分かりづらいですが、以下が影をベイクしたテクスチャ用の画像です。

テクスチャをPlateに設定します。

問題としては、テクスチャなので、ライトやオブジェクトの位置を替えても影の位置は変わりません。
◎アルファマップを利用する
以下のような、アルファマップを利用して、プレートに影を表示します。

黒ベタの影用のプレートを用意して、アルファマップで影の部分だけ表示させます。
const plane = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
plane.rotation.x = -Math.PI * 0.5;
plane.position.y = -0.5;
// アルファマップを読み込み
const alphaMap = new THREE.TextureLoader().load("/textures/alphaMap.png");
// 影用のplateを作成
const shadowPlane = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
new THREE.MeshBasicMaterial({ color: 0x000000, alphaMap, transparent: true })
);
shadowPlane.rotation.x = -Math.PI * 0.5;
shadowPlane.position.y = -0.499;
結果はこんな感じ。

色も簡単に変更できます。
new THREE.MeshBasicMaterial({ color: 0xf600ff, alphaMap, transparent: true })

影はパフォーマンスへの影響が多きそうなので、影をベイクした画像をりようするなど、なるべくレンダリングが発生しないような工夫が必要そうです。