こんな需要があるかはわかりませんが、前々回と前回の記事を併せれば実現できます。
blog.kimizuka.org
blog.kimizuka.org
つまり、透明なオブジェクトに対してcastShadowを有効にしてあげれば影だけが投影されるのです。
透明なのに影が映るのはおかしい気もしますが、おそらくテクスチャは影に影響しないのだと思われます。
折角動的に影を付けているので、モデルをアニメーションさせてみると、
しっかり動きます。
これを応用して、これの影を、
3Dモデルと影の動きを同期させて、ARとプロジェクションで合成する。 pic.twitter.com/8GAX38fR0B
— 君塚史高 (@ki_230) 2021年6月16日
もっとリアルにしました。
ARオブジェクト + プロジェクションした影。
— 君塚史高 (@ki_230) 2021年7月15日
プロジェクタの位置を決めて、影のリアリティを向上させる。 pic.twitter.com/DJojcrkiuP
前回までのものは、モデルのテクスチャを真っ黒にしたものを影として投影していましたが、今回のものは影を動的に計算したものを使っています。
いつものごとく、一応、ソースを全部載せておきます。
同一階層に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>