Description of the problem
I have a card with a light shining on it. There’s also a plane behind that card and I want it to receive the card’s shadow. I’ve done my best to create a minimal example. There are two problems.
Problem 1
After loading the glTF file with the GLTFLoader
, I traverse all objects and set castShadow
and receiveShadow
to true
. The shadow doesn’t appear. However, if I create a new PointLight
, the shadow appears. It seems that the light that from the glTF file can’t cast a shadow for some reason. Why?
Problem 2
In Blender, I’ve set up a camera that looks at the scene. When I try to render with it, I see nothing. Why does that happen? The only workaround is to create a new camera and point it manually.
Here’s my main script:
import * as THREE from '../build/three.module.js';
import { OrbitControls } from './jsm/controls/OrbitControls.js';
import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
var container, controls;
var camera, scene, renderer;
init();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
scene = new THREE.Scene();
var loader = new GLTFLoader();
loader.load('./models/card.glb', function (gltf) {
// Problem 1: The only way to get a shadow is by creating a light in THREE, rather
// than using the one from glTF.
//
// let light = new THREE.PointLight(0xffffff, 0.5)
// light.position.set(0, 5, 3)
// scene.add(light)
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.25, 20);
camera.position.set(0, 0.2, 0.4);
camera.rotation.set(10, 10, 10)
// Problem 2: Doesn't work with the Blender camera
// camera = gltf.cameras[0]
scene.add(gltf.scene);
controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, - 0.2);
controls.update();
// Add shadows to all objects, including lights
scene.traverse(function (obj) {
obj.castShadow = true
obj.receiveShadow = true
console.log('setting shadow to', obj)
})
render();
});
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.outputEncoding = THREE.sRGBEncoding
renderer.shadowMap.enabled = true
container.appendChild(renderer.domElement);
}
function render() {
renderer.render(scene, camera);
window.requestAnimationFrame(render)
}
Here’s how the scene looks with the external PointLight:
Here’s how the scene looks without the external PointLight, i.e. using only the light from Blender:
There’s a shadow in the first image, and there’s no shadow in the second. I want to have a shadow without creating any new lights. I want to just load the .glb
file, set castShadow
and receiveShadow
, and have the shadows appear.
Resources
I’m including a file with an ES6 modules example. I’ve included both the .glb
file and .blend
file there as well. Simply spin up a local server and open the examples/card.html
file.
card-test.zip
Three.js version
I cloned this repo and put my experiment in the /examples
folder.
Browser
- All of them
- Chrome
- Firefox
- Internet Explorer
OS
- All of them
- Windows
- macOS
- Linux
- Android
- iOS
If you reposition the light with
light.position.y += 0.5
you’ll see the shadow, so I think this is a matter of configuring shadow settings for the relatively small scales involved. This change will show a shadow:I also added a sphere to show the light’s position:
You may find the CameraHelper class helpful in debugging both
light.shadow.camera
and the camera you render with.