Camera get messed up on resize when directional light is in scene and shadowmap is enabled in mapbox

I have set up a three scene in mapbox with shadows. The shadows works fine:

The problem arises when I resize mapbox:

This is how I set up my scene in mapbox:

var modelTransform = getModelTransform(location);

    var customLayer = {
      id: "3d-model",
      type: "custom",
      renderingMode: "3d",
      onAdd: function (threeMap, gl) {
        camera = new THREE.Camera();

        map = threeMap;

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

        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        this.renderer.autoClear = false;

      },
      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);

        camera.projectionMatrix = m.multiply(l);
        this.renderer.state.reset();
        this.renderer.render(scene, camera);
        scene.add(createBounds(params.location, params.bounds, THREE))
        //map.triggerRepaint();
      }
    };

    map.on("style.load", () => {
      map.addLayer(customLayer, "waterway-label");
    });

The onAdd is called once when the page is loaded and render when something changes in mapbox. If I place the code for initializing the renderer in render it works but it’s very laggy.

This issue only occurs if I have both the shadowmap enabled and have a visible directional light in the scene. Any idea what might cause this?

I found a solution. It seems when I set the renderer it is configured to the canvas current height and with so what I did was to check every render call if the height or width changes and if it does I dispose of the old renderer and create a new one.

Hi, can you explain how you’re recreating the renderer? I tried nulling it and recreating it, but when i do this the map is black as they both use the camera and renderer.

Thanks

My map and three scene is separate. I use a custom mapbox layer with a three scene and then I modify the contents of the three scene. In the onAdd method I create a renderer and in the render function if the map.getCanvas width or height has changed I remove the renderer and replace it with a new one. I use this function to create renderer:

function createRenderer(map, gl) {
  rendererTracker.dispose();
  const renderer = track(
    new THREE.WebGLRenderer({
      canvas: map.getCanvas(),
      context: gl,
      antialias: true,
    })
  );

  return renderer;
}

The rendererTracker is an instance of a class:

class ResourceTracker {
  constructor() {
    this.resources = new Set();
  }
  track(resource) {
    if (resource.dispose || resource instanceof THREE.Object3D) {
      this.resources.add(resource);
    }
    return resource;
  }
  untrack(resource) {
    this.resources.delete(resource);
  }
  dispose() {
    for (const resource of this.resources) {
      if (resource instanceof THREE.Object3D) {
        if (resource.parent) {
          resource.parent.remove(resource);
        }
      }
      if (resource.dispose) {
        resource.dispose();
      }
    }
    this.resources.clear();
  }
}

The resource tracker is used to clear three resources from memory. To use it you need to call track() for each resource you want to be able to clear later. Just keep in mind that if you call dispose it removes everything tracked so make sure to create multiple trackers if you want to dispose different resources separately.