Model not casting/receiving shadows

Good day all.

I am relatively new to Three.js. But all my research regarding this issue has ended in a mute point. I have loaded in to my scene a gLTF model, and although all the meshes do reflect a sense of shadows, they do not cast shadows on the other meshes in the scene. I am using a spotlight to emulate a desk lamp. Also, despite ensuring that all castShadows and receiveShadows nodes are set to ‘true’, I am still unable to emulate true lighting atmosphere. Please see code snippet below for more detail.

let renderer = new THREE.WebGLRenderer( { canvas: document.getElementById( 'mainBody' ), antialias: true } );
renderer.setClearColor( 0x000000 );
renderer.setPixelRatio( window.devicePicelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
renderer.gammaOutput = true;
renderer.gammaFactor = 2.2;
window.addEventListener( 'resize', onWindowResize, false );

let camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 3000 );
camera.position.x = 800;
camera.position.y = 350;
camera.position.z = 500;

let controls = new THREE.OrbitControls( camera );
// camera.position.set(0, 0, 0);
controls.update();

let scene = new THREE.Scene();

function addLights() {
  let light1 = new THREE.SpotLight( 0xffffff, 0.8 );              //desklamp spotlight
  light1.name = 'Desk Lamp';
  light1.penumbra = 0.3;
  light1.position.set( 263, 173, 420 );
  light1.target.position.set( 300, 140, 420 );
  light1.angle = Math.PI * 3;
  light1.castShadow = true;
  light1.receiveShadow = true;
  light1.shadow.camera.near = 0.5;       // default
  light1.shadow.camera.far = 350      // default
  light1.shadow.mapSize.width = 512;  // default
  light1.shadow.mapSize.height = 512; // default
  scene.add( light1 );
  scene.add( light1.target );

  let helper = new THREE.CameraHelper ( light1.shadow.camera );
  scene.add( helper );

  // let light2 = new THREE.AmbientLight( 0xffffff, 0.1 );          used for testing light
  // light2.position.set( 800, 200, 800 );

  // scene.add( light2 );
}

addLights();
var loader = new THREE.GLTFLoader();

loader.load( 'assets/scene.gltf', function ( gltf ) {               //model from sketchfab
  gltf.scene.name = 'study';
  gltf.scene.position.x = 0;
  gltf.scene.position.y = 0;
  gltf.scene.position.z = 1000;
  gltf.scene.traverse( function( child ){ child.castShadow = true; } );
  gltf.scene.traverse( function( child ){ child.receiveShadow = true; } );
	scene.add( gltf.scene );
  console.log( scene );

}, undefined, function ( error ) {

	console.log( error );

} );

function onWindowResize() {
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();
				renderer.setSize( window.innerWidth, window.innerHeight );
}

function animate() {
  requestAnimationFrame( animate );
  renderer.render( scene, camera );
}


animate();

P.S. Apologies for the format if incorrect. Still new to this. Any advice for future posts welcome.

Without a live example I can only provide some basic suggestions.

  • Ensure that the shadow frustum of your camera is large enough so all shadow casting and receiving objects are actually contained in it. Since you are already using CameraHelper, it should be easy to verify this.
  • Use the ambient light not only for debugging/testing. A scene with just a spot light looks often very weird.
  • Your traverse function can be optimized. You don’t have to do it two times. Try it like so:
const scene = gltf.scene;

scene.traverse( function( child ) { 

    if ( child.isMesh ) {

        child.castShadow = true;
        child.receiveShadow = true;

    }

} );

Hi there.

Thank you for the response. Here is my feedback:

  1. Checked the frustam and confirmed all should be casting within the lights range.
  2. Thanks for the tip on the ambient light. I will keep that in mind during the development.
  3. I have optimized my traverse function as you recommended. In doing so, I found that the model is not a mesh. This could potentially be the cause of my lack of shadows. Unfortunately I have no idea on how to resolve this. Any suggestions?

Once again. Thank you.

Update.

I have imported the model into Blender. It appears that the model is composed of polysurfaces. Will three.js allow shadows on these surfaces?

Can you please share the glTF in this thread? Besides, how have you imported the model in Blender?

assets.rar (2.8 MB)

Here is the model. Unfortunately, I was unable to include the textures as the file ended up too big to upload. As for the import into blender, I simply went file>import>gLTF and selected the gLTF file.

Are you using this model?

That is it. yes.

I understand that the scene still requires post processing etc. But on my scene, the shadows around the pages on the desk as well as the drawers don’t appear at all.

The following code produces this result:

image

Notice how the scene is automatically centered and the camera configured according to the model’s dimensions.

var container, stats, controls;
var camera, scene, renderer;

init();
animate();

function init() {

    container = document.createElement( 'div' );
    document.body.appendChild( container );

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 20 );
    camera.position.set( - 1.8, 0.9, 2.7 );

    controls = new THREE.OrbitControls( camera );

    var ambientLight = new THREE.AmbientLight( 0xffffff, 0.4 );
    scene.add( ambientLight );

    var spotLight = new THREE.SpotLight( 0xffffff, 1 );
    spotLight.position.set( 500, 400, 200 );
    spotLight.angle = 0.4;
    spotLight.penumbra = 0.05;
    spotLight.decay = 1;
    spotLight.distance = 2000;

    spotLight.castShadow = true;
    scene.add( spotLight );

    spotLight.target.position.set( 3, 0, - 3 );
    scene.add( spotLight.target );

    var lightHelper = new THREE.SpotLightHelper( spotLight );
    // scene.add( lightHelper );

    // model
    var loader = new THREE.GLTFLoader().setPath( 'models/gltf/test/' );
    loader.load( 'scene.gltf', function ( gltf ) {

        gltf.scene.traverse( function ( child ) {

            if ( child.isMesh ) {

                child.castShadow = true;
                child.receiveShadow = true;

            }

        } );

        // automatically center model and adjust camera

        const box = new THREE.Box3().setFromObject( gltf.scene );
        const size = box.getSize( new THREE.Vector3() ).length();
        const center = box.getCenter( new THREE.Vector3() );

        gltf.scene.position.x += ( gltf.scene.position.x - center.x );
        gltf.scene.position.y += ( gltf.scene.position.y - center.y );
        gltf.scene.position.z += ( gltf.scene.position.z - center.z );

        camera.near = size / 100;
        camera.far = size * 100;

        camera.updateProjectionMatrix();

        camera.position.copy( center );
        camera.position.x += size / 2.0;
        camera.position.y += size / 5.0;
        camera.position.z += size / 2.0;
        camera.lookAt( center );

        console.log( camera.position );

        controls.maxDistance = size * 10;
        controls.update();

        scene.add( gltf.scene );

    }, undefined, function ( e ) {

        console.error( e );

    } );

    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.gammaOutput = true;
    renderer.gammaFactor = 2.2;
    renderer.shadowMap.enabled = true;
    container.appendChild( renderer.domElement );

    window.addEventListener( 'resize', onWindowResize, false );

    // stats
    stats = new Stats();
    container.appendChild( stats.dom );

}

function onWindowResize() {

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );

}

//

function animate() {

    requestAnimationFrame( animate );

    renderer.render( scene, camera );

    stats.update();

}
4 Likes

Well look at you fancy pants!!

That’s exactly what I was looking for. Thank you so very much for assisting. As I understand from the code you added, you’ve converted the scene to a THREE.box3 object to represent the minimum bounding boxes and manipulated it from there. Is that correct?

Thank you once again. You’re a champion!

1 Like

It’s an AABB that is used to center the scene. This makes it easier to work with e.g. when adding lights.