見出し画像

[UIデザイナーが学ぶ]Three.jsの影の基本的な話

普段はメーカーで影の薄いUIデザイナーとして勤務しております。

あまり業務では利用しませんが、Three.jsの影について学び直しているので、備忘録的にまとめています。


影の表示手順

  1. レンダラーで影を有効にする。

  2. ライトで影を有効にする。

  3. 影を落とすMeshを設定する

  4. 影を受ける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を小さい値にした場合です、影が途切れているのがわかります。

farを4にした場合

レンダリング範囲を小さくすることで、影の品質が向上します。


影をぼかす

radiusプロパティを利用すると、影のぼかし効果を調整することができます。このプロパティを設定することで、影のエッジを滑らかにすることができます。

ただし、radiusプロパティのぼかしは物体の距離によって変化しません
物体が遠いところにあろうと、近いところにあろうと常に一定の表示となります。

directionalLight.shadow.radius = 5

シャドウマップの種類

shadowMap.type は、Three.js で使用されるシャドウマップの種類を指定するプロパティです。シャドウマップの種類によって、影の品質やパフォーマンスが異なります。以下に主要なシャドウマップの種類を説明します:

  1. THREE.BasicShadowMap:

    • 最も基本的なシャドウマップ。

    • パフォーマンスは高いが、影の品質は低い。

    • エッジがギザギザになることがある。

  2. THREE.PCFShadowMap:

    • Percentage-Closer Filtering (PCF) を使用したシャドウマップ。

    • 影のエッジが滑らかになる。

    • パフォーマンスと品質のバランスが良い。

  3. THREE.PCFSoftShadowMap:

    • PCF をさらに改良したソフトシャドウマップ。

    • 影のエッジがさらに滑らかで、柔らかい影を生成する。

    • パフォーマンスはやや低いが、影の品質は高い。

  4. THREE.VSMShadowMap:

    • Variance Shadow Maps (VSM) を使用したシャドウマップ。

    • 高品質な影を生成し、光の漏れを防ぐ。

    • パフォーマンスはやや低いが、特定のシーンで非常に効果的。

デフォルトはTHREE.PCFShadowMap。
品質を向上させたかったら、PCFSoftShadowMapを利用するのが良さそう。
だたし、PCFSoftShadowMapはradiusは利用できません。

影のパフォーマンスを考える

複数のライトを追加して影を作成する場合、パフォーマンスに影響を与える可能性があります。

  1. シャドウマップの数:

    • 各ライトが影を生成するためにシャドウマップを作成します。ライトの数が増えると、シャドウマップの数も増え、レンダリングの負荷が高くなります。

  2. シャドウマップの解像度:

    • 高解像度のシャドウマップは影の品質を向上させますが、メモリ使用量とレンダリング時間が増加します。必要に応じて解像度を調整してください。

  3. シャドウマップの種類:

    • THREE.PCFSoftShadowMap や THREE.VSMShadowMap などの高品質なシャドウマップは、影の品質を向上させますが、パフォーマンスに影響を与える可能性があります。シーンに応じて適切なシャドウマップの種類を選択してください。

  4. ライトの種類:

    • 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 })

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

いいなと思ったら応援しよう!