2

I have 3D models as such:

model

I want to add a cast shadow similar to this:

shadow

And I have the following piece of code responsible for the model:

var ambLight = new THREE.AmbientLight( 0x404040 );
this.scene.add(ambLight)

var loader = new THREE.GLTFLoader();
loader.load(path,function (gltf) {
 gltf.scene.traverse( function( model ) {
  if (model.isMesh){ 
   model.castShadow = true; 
  }
 });
 this.scene.add(gltf.scene);
}

I added the castSHadow part as seen in this StackOverflow post.

I've tried model.castShadow = true and I've tried removing the if condition and just leave the castShadow but that doesn't work either. Am I missing a step? The full custom layer code is here if it helps.

TiagoRibeiro
  • 306
  • 6
  • 24
  • For one, you need to also set `.receiveShadow = true` for the ground, etc. I played around with shadows once and it's more complex than I thought :) There's probably not a quick and simple answer to this question; I'd also advise you to do more research first. –  Jan 10 '21 at 16:20

2 Answers2

5
  • You only have an instance of AmbientLight in your scene which is no shadow-casting light.
  • 3D objects can only receive shadow if they set Object3D.receiveShadow to true and if the material is not unlit. Meaning MeshBasicMaterial would not work as the ground's material.
  • You have to globally enable shadows via: renderer.shadowMap.enabled = true;

I suggest you have a closer look to the shadow setup of this official example.

Mugen87
  • 28,829
  • 4
  • 27
  • 50
  • 1
    I've updated the code , check here: https://codepen.io/TiagoRibeiro94/pen/XWjPXzE?editors=0010 Yet, it doesn't work /: I've added the shadowMap.enabled as suggested, switched ambientLight to a directional one. I've very new to js in particular, but even more to three.js, so I'm at a lost atm. Any clue what the issue could be? – TiagoRibeiro Jan 11 '21 at 01:21
  • Depending on your scene, the settings for the shadow camera's frustum are not yet correct. I suggest you visually debug the frustum by add this line of code to your app: `scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );`. Then ensure that you scene lies within the visualized box. – Mugen87 Jan 11 '21 at 10:08
5

Based on your code, you're trying to add a shadow on top of Mapbox.

For that, apart from the suggestions from @Mugen87, you'll need to create a surface to receive the shadow, and place it exactly below the model you're loading, considering also the size of the object you're loading to avoid the shadow goes out of the plane surface... and then you'll get this.

enter image description here

Relevant code in this fiddle I have created. I slightly changed the light and I added a light helper for clarity.

var customLayer = {
  id: '3d-model',
  type: 'custom',
  renderingMode: '3d',
  onAdd: function(map, gl) {
    this.camera = new THREE.Camera();
    this.scene = new THREE.Scene();

    const dirLight = new THREE.DirectionalLight(0xffffff, 1);
    dirLight.position.set(0, 70, 100);
    let d = 1000;
    let r = 2;
    let mapSize = 8192;
    dirLight.castShadow = true;
    dirLight.shadow.radius = r;
    dirLight.shadow.mapSize.width = mapSize;
    dirLight.shadow.mapSize.height = mapSize;
    dirLight.shadow.camera.top = dirLight.shadow.camera.right = d;
    dirLight.shadow.camera.bottom = dirLight.shadow.camera.left = -d;
    dirLight.shadow.camera.near = 1;
    dirLight.shadow.camera.far = 400000000;
    //dirLight.shadow.camera.visible = true;

    this.scene.add(dirLight);
    this.scene.add(new THREE.DirectionalLightHelper(dirLight, 10));


    // use the three.js GLTF loader to add the 3D model to the three.js scene
    var loader = new THREE.GLTFLoader();
    loader.load(
      'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
      function(gltf) {
        gltf.scene.traverse(function(model) {
          if (model.isMesh) {
            model.castShadow = true;
          }
        });
        this.scene.add(gltf.scene);
        // we add the shadow plane automatically 
        const s = new THREE.Box3().setFromObject(gltf.scene).getSize(new THREE.Vector3(0, 0, 0));
        const sizes = [s.x, s.y, s.z];
        const planeSize = Math.max(...sizes) * 10;
        const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
        const planeMat = new THREE.ShadowMaterial();
        planeMat.opacity = 0.5;
        let plane = new THREE.Mesh(planeGeo, planeMat);
        plane.rotateX(-Math.PI / 2);
        plane.receiveShadow = true;
        this.scene.add(plane);
      }.bind(this)
    );
    this.map = map;

    // use the Mapbox GL JS map canvas for three.js
    this.renderer = new THREE.WebGLRenderer({
      canvas: map.getCanvas(),
      context: gl,
      antialias: true
    });

    this.renderer.autoClear = false;
    this.renderer.shadowMap.enabled = true;

  },
  render: function(gl, matrix) {
    var rotationX = new THREE.Matrix4().makeRotationAxis(
      new THREE.Vector3(1, 0, 0),
      modelTransform.rotateX
    );
    var rotationY = new THREE.Matrix4().makeRotationAxis(
      new THREE.Vector3(0, 1, 0),
      modelTransform.rotateY
    );
    var rotationZ = new THREE.Matrix4().makeRotationAxis(
      new THREE.Vector3(0, 0, 1),
      modelTransform.rotateZ
    );

    var m = new THREE.Matrix4().fromArray(matrix);
    var l = new THREE.Matrix4()
      .makeTranslation(
        modelTransform.translateX,
        modelTransform.translateY,
        modelTransform.translateZ
      )
      .scale(
        new THREE.Vector3(
          modelTransform.scale,
          -modelTransform.scale,
          modelTransform.scale
        )
      )
      .multiply(rotationX)
      .multiply(rotationY)
      .multiply(rotationZ);

    this.camera.projectionMatrix = m.multiply(l);
    this.renderer.state.reset();
    this.renderer.render(this.scene, this.camera);

    this.map.triggerRepaint();
  }
};
jscastro
  • 3,422
  • 1
  • 9
  • 27