Three.js: Blender-generated GLB file loaded with GLTFLoader has lights that can't cast shadows

Created on 5 Feb 2020  路  3Comments  路  Source: mrdoob/three.js

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:

image

Here's how the scene looks without the external PointLight, i.e. using only the light from Blender:

image

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
  • [x] Chrome
  • [x] Firefox
  • [ ] Internet Explorer
OS
  • [ ] All of them
  • [x] Windows
  • [ ] macOS
  • [ ] Linux
  • [ ] Android
  • [ ] iOS
Help (please use the forum)

Most helpful comment

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:

scene.traverse(function (obj) {
  if (obj.isLight) {
    obj.shadow.camera.near = 0.001;
    obj.shadow.camera.updateProjectionMatrix();
  } else if (obj.isMesh) {
    obj.castShadow = true
    obj.receiveShadow = true
  } 
})

I also added a sphere to show the light's position:

Screen Shot 2020-02-05 at 9 21 37 AM

All 3 comments

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:

scene.traverse(function (obj) {
  if (obj.isLight) {
    obj.shadow.camera.near = 0.001;
    obj.shadow.camera.updateProjectionMatrix();
  } else if (obj.isMesh) {
    obj.castShadow = true
    obj.receiveShadow = true
  } 
})

I also added a sphere to show the light's position:

Screen Shot 2020-02-05 at 9 21 37 AM

You may find the CameraHelper class helpful in debugging both light.shadow.camera and the camera you render with.

Closing this out as it doesn't appear to be a threejs bug, but feel free to ask followup questions on the forums: https://discourse.threejs.org/.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

danieljack picture danieljack  路  3Comments

zsitro picture zsitro  路  3Comments

ghost picture ghost  路  3Comments

konijn picture konijn  路  3Comments

seep picture seep  路  3Comments