みかづきブログ・カスタム

基本的にはちょちょいのほいです。

Three.jsに読み込んだglbファイルの影のみを他のオブジェクトに投影し、glbファイル自体は表示しない 🔦

f:id:kimizuka:20210719010335p:plain

こんな需要があるかはわかりませんが、前々回前回の記事を併せれば実現できます。

blog.kimizuka.org
blog.kimizuka.org

つまり、透明なオブジェクトに対してcastShadowを有効にしてあげれば影だけが投影されるのです。
透明なのに影が映るのはおかしい気もしますが、おそらくテクスチャは影に影響しないのだと思われます。

折角動的に影を付けているので、モデルをアニメーションさせてみると、

f:id:kimizuka:20210719093136g:plain

しっかり動きます。

これを応用して、これの影を、

もっとリアルにしました。

前回までのものは、モデルのテクスチャを真っ黒にしたものを影として投影していましたが、今回のものは影を動的に計算したものを使っています。

いつものごとく、一応、ソースを全部載せておきます。
同一階層にmodel.glbを置けば動くと思います。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>cast-shadow</title>
  <style>
    * {
      margin: 0; padding: 0;
    }

    canvas {
      display: block;
    }
  </style>
</head>
<body>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r127/three.min.js"></script>
  <script src="https://cdn.rawgit.com/mrdoob/three.js/r127/examples/js/loaders/GLTFLoader.js"></script>
  <script>
    let mixer;
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(
      60,
      window.innerWidth / window.innerHeight,
      1,
      100
    );

    camera.position.set(0, 1, 3);
    scene.add(camera);

    const renderer = new THREE.WebGLRenderer({
      antialias : true,
      alpha: true
    });

    renderer.shadowMap.enabled = true;
    renderer.outputEncoding = THREE.GammaEncoding;
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);

    document.body.appendChild(renderer.domElement);

    const loader = new THREE.GLTFLoader();
    const url = './model.glb';

    loader.load(
      url,
      (gltf) => {
        const animations = gltf.animations;
        const model = gltf.scene;

        if (animations && animations.length) {
          mixer = new THREE.AnimationMixer(model);

          for (let i = 0; i < animations.length; i++) {
            const animation = animations[i];
            const action = mixer.clipAction(animation);

            action.play();
          }
        }

        model.traverse((object) => {
          if (object.isMesh) {
            object.castShadow = true;
            object.material.trasparent = true;
            object.material.alphaToCoverage = true;
            object.material.opacity = 0;
          }
        });

        model.scale.set(1, 1, 1);
        model.position.set(0, 0, 0);

        scene.add(model);

        renderer.setAnimationLoop(tick);
      },
      (err) => {
        console.error(err);
      }
    );

    const light = new THREE.AmbientLight(0xFFFFFF, .8);

    scene.add(light);

    (() => {
      const light = new THREE.DirectionalLight(0xFFFFFF, 1);

      light.position.set(1, 0, 1);

      light.castShadow = true;
      light.shadowMapWidth = 4096;
      light.shadowMapHeight = 4096;

      scene.add(light);
    })();

    const geometry = new THREE.PlaneGeometry(16, 16, 1, 1);
    const material = new THREE.MeshStandardMaterial({
      color: 0xFF0000
    });
    const wall = new THREE.Mesh(geometry, material);

    wall.position.set(0, 0, -1);
    wall.rotation.set(0, 0, 0);
    wall.receiveShadow = true;

    scene.add(wall);

    function tick() {
      if (mixer) {
        mixer.setTime(Date.now() / 1000);
      }

      renderer.render(scene, camera);
    }
  </script>
</body>
</html>