【Three.js】ライトで影を落とす方法と影の調整のやり方(デモつき)

WebGL & Three.js

Three.jsでライトを使ったとき、物体の影を落とす方法を紹介します。
また影を薄くしたり、濃くしたり、影がギザギザのときの対処方法といった調整のやり方も紹介しています。
まずはサンプルデモがあるので、以下をご確認ください。

ライトで影を落とすサンプルデモ


PointLight(点光源)をy軸を中心にしてゆっくりと回転させています。
最初の方は影がでない状態で、そこから以下で説明することを設定することで、二つの目のデモのようにライトで影を落とすことができるようになります。

ソースコード

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );

  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 影を有効にする
  renderer.shadowMap.enabled = true;
  // 背景色を変更する
  renderer.setClearColor(0xf0f0f0);
  document.body.appendChild(renderer.domElement);

  // 床
  const floor = new THREE.Mesh(
    new THREE.PlaneGeometry(1000, 1000),
    new THREE.MeshStandardMaterial({
      color: 0x777777,
      side: THREE.DoubleSide,
    })
  );

  floor.rotation.x = THREE.MathUtils.degToRad(90);
  floor.position.y = -60;
  // 影を受け取る物体のreceiveShadowをtrueにする
  floor.receiveShadow = true;
  scene.add(floor);

  // メッシュ作成
  const sphereGeometry = new THREE.SphereGeometry(5);
  // パス「ん」
  const path = new THREE.CatmullRomCurve3([
    new THREE.Vector3(0.0, 15.0, 0.0),
    new THREE.Vector3(-12.5, -10.0, -2.0),
    new THREE.Vector3(-12.5, -10.0, 4.25),
    new THREE.Vector3(-6.25, 0.5, 3.0),
    new THREE.Vector3(-2.0, 0.5, 3.0),
    new THREE.Vector3(2.0, -8.0, 2.0),
    new THREE.Vector3(8.25, -10.0, 1.0),
    new THREE.Vector3(12.5, -3.75, 0.0),
  ]);
  const tube = new THREE.TubeGeometry(path, 6, 3, 3, false, true);

  const torusKnot = new THREE.TorusKnotGeometry(5, 1.4, 160, 160);
  const phong = new THREE.MeshPhongMaterial({ color: 0x3fff9d });
  const lambert = new THREE.MeshLambertMaterial({ color: 0x3fff9d });
  const standard = new THREE.MeshStandardMaterial({
    color: 0x3fff9d,
    roughness: 0,
  });

  const mesh1 = new THREE.Mesh(sphereGeometry, phong);
  mesh1.position.x -= 20;
  // 影を落としたい物体のcastShadowをtrue
  mesh1.castShadow = true;

  const mesh2 = new THREE.Mesh(tube, lambert);
  mesh2.castShadow = true;

  const mesh3 = new THREE.Mesh(torusKnot, standard);
  mesh3.position.x += 20;
  mesh3.castShadow = true;

  scene.add(mesh1, mesh2, mesh3);

  // 軸ヘルパー
  const axis = new THREE.AxesHelper(1000);
  scene.add(axis);

  camera.position.z = 100;

  const control = new OrbitControls(camera, renderer.domElement);

  // ライト
  // AmbientLight参考: https://threejs.org/docs/index.html?q=ambi#api/en/lights/AmbientLight
  // AmbientLight:シーン全体を全方位から均等に照らすライト
  const ambientLight = new THREE.AmbientLight(0xf0f0f0);
  scene.add(ambientLight);

  // 点光源
  // 影が薄いときは、ライトのdistanceの部分を大きくする
  // 0の場合(デフォルト)は無限遠(無限に遠い)設定なので影ができる
  // const pointLight = new THREE.PointLight(0xffffff, 1, 200);
  const pointLight = new THREE.PointLight(0xffffff, 1, 500);
  pointLight.position.set(0, 0, 4);
  // 影を有効にするライトのcastShadowをtrueにする(pointLightかspotLight)
  pointLight.castShadow = true;
  // 影の粗さを調整
  // GPUに負荷がかかるのであまり大きい数値にしすぎない方がいい
  pointLight.shadow.mapSize.width = 1024;
  pointLight.shadow.mapSize.height = 1024;

  // 回転させる
  // radius, phi(x軸への回転), theta(y軸への回転)
  const spherical = new THREE.Spherical(28, 0.5, 1);
  pointLight.position.setFromSpherical(spherical);

  // 点光源のヘルパー
  const pHelper = new THREE.PointLightHelper(pointLight);
  scene.add(pointLight, pHelper);

  function animate() {
    requestAnimationFrame(animate);
    // 点光源を回転させる
    spherical.theta += 0.01;
    pointLight.position.setFromSpherical(spherical);
    control.update();

    renderer.render(scene, camera);
  }

  animate();

今回いろいろなジオメトリを使用しています。
SphereGeometryは球体、「ん」のような形はTubeGeometry、最後に知恵の輪のようなTorusKnotGeometryを使っています。
ジオメトリがよくわからない場合は、Geometry(ジオメトリ)の種類と変更方法-3Dモデルのデモ付きで紹介を参考にしてみてください。
また上のコードの基本的な説明は、基本のライト:AmbientLight, DirectionalLight, PointLightの使い方とサンプルデモで説明しています。

今回のライトで影を落とすポイントは以下です。

renderer.shadowMap.enabledをtrueにする

  // 影を有効にする
  renderer.shadowMap.enabled = true;

レンダラーで影を使えるようにshadowMap.enabledをtrueに設定します。

影を写したい物体のreceiveShadowをtrueにする

  // 影を受け取る物体のreceiveShadowをtrueにする
  floor.receiveShadow = true;

影を受け取る物体、上のケースでは床に使っているfloorのreceiveShadowをtrueにして影を受け取れるようにします。

影を落としたい物体のcastShadowをtrueにする

  // 影を落としたい物体のcastShadowをtrue
  mesh1.castShadow = true;
  mesh2.castShadow = true;
  mesh3.castShadow = true;

次に、影を落としたい物体のcastShadowをtrueにして、影ができるようにします。

影を落とすライトの設定

影を落とすことができるのは、いろいろなライトがありますが、最もよく使われるのは、PointLight(点光源)とSpotLight(スポットライト)です。
上のケースでは、PointLightで説明しています。

ライトの影を有効にする

  // 影を有効にするライトのcastShadowをtrueにする(pointLightかspotLight)
  pointLight.castShadow = true;

ライトのcastShadowをtrueにして影ができるようにします。

影の距離を調整する

  // 影が薄いときは、ライトのdistanceの部分を大きくする
  // const pointLight = new THREE.PointLight(0xffffff, 1, 200);
  const pointLight = new THREE.PointLight(0xffffff, 1, 500);

上の場合点光源で影の距離は、第三引数の値です。
0の場合(デフォルト)は無限遠(無限に遠い)設定なので影ができる設定になります。
もし距離を設定していて影が薄い場合は、値を大きくすることで影をくっきりさせることができます。

ギザギザの影の粗さを調整する

  // 影の粗さを調整
  pointLight.shadow.mapSize.width = 1024;
  pointLight.shadow.mapSize.height = 1024;

デフォルト値の場合、影がギザギザになってしまうことがあります。
その場合あ、mapSizeのwidthとheightの値を大きくすることで影のギザギザとした粗さを軽減することができます。
ただし、GPUに負荷がかかるのであまり大きい数値にしすぎない方がいいです。