从 Three.js v122 更改为 v123 隐藏了 Mapbox 自定义图层上的阴影

Change from Three.js v122 to v123 hides the shadows on a custom layer for Mapbox

我正在使用 three.js 向 mapbox 添加自定义层,使用 v122 中的 official example from mapbox. I found that the shadows that worked perfectly in v122 are not working in v123. After reading carefully the release changelog for v123 and and the migration guide,我找不到任何相关的提交或更改使阴影消失。我还测试了 three.js 的最新可用版本,情况相同,但我发现这两个版本之间发生了变化。我用 ShadowMaterial 以外的不同材料进行了测试,但结果相同。

完全相同的代码,只是更改了包版本:

https://unpkg.com/three@0.122.0/examples/js/loaders/GLTFLoader.js
https://unpkg.com/three@0.122.0/build/three.min.js

https://unpkg.com/three@0.123.0/examples/js/loaders/GLTFLoader.js
https://unpkg.com/three@0.123.0/build/three.min.js

来自这里:
这是 Fiddle v122

为此...
这是 Fiddle v123

代码完全一样:

mapboxgl.accessToken = 'pk.eyJ1IjoianNjYXN0cm8iLCJhIjoiY2s2YzB6Z25kMDVhejNrbXNpcmtjNGtpbiJ9.28ynPf1Y5Q8EyB_moOHylw';
var map = (window.map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/light-v10',
  zoom: 18,
  center: [148.9819, -35.3981],
  pitch: 60,
  bearing: 45,
  antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
}));

// parameters to ensure the model is georeferenced correctly on the map
var modelOrigin = [148.9819, -35.39847];
var modelAltitude = 0;
var modelRotate = [Math.PI / 2, 0, 0];

var modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
  modelOrigin,
  modelAltitude
);

// transformation parameters to position, rotate and scale the 3D model onto the map
var modelTransform = {
  translateX: modelAsMercatorCoordinate.x,
  translateY: modelAsMercatorCoordinate.y,
  translateZ: modelAsMercatorCoordinate.z,
  rotateX: modelRotate[0],
  rotateY: modelRotate[1],
  rotateZ: modelRotate[2],
  /* Since our 3D model is in real world meters, a scale transform needs to be
   * applied since the CustomLayerInterface expects units in MercatorCoordinates.
   */
  scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};

var THREE = window.THREE;

// configuration of the custom layer for a 3D model per the CustomLayerInterface
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 = 100;
    let r = 2;
    let mapSize = 1024;
    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 = 400;
    dirLight.shadow.camera.visible = true;

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

    // 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.MeshStandardMaterial({ color: 0xffffff, side: THREE.DoubleSide});
        const planeMat = new THREE.ShadowMaterial();
        planeMat.opacity = 0.5;
        let plane = new THREE.Mesh(planeGeo, planeMat);
        plane.rotateX(-Math.PI / 2);
        //plane.layers.enable(1); plane.layers.disable(0); // it makes the object invisible for the raycaster
        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();
  }
};

map.on('style.load', function() {
  map.addLayer(customLayer, 'waterway-label');
});

这似乎是一个错误,但老实说,我喜欢的是我遗漏或没有意识到的东西。我什至检查了来自不同 CDN 的文件是否发生相同的情况,是的,它发生的情况相同。希望 Three.js 贡献者之一或其他开发人员可以帮助我解决这个问题,因为我完全受阻并阻止我迁移

提前感谢您的指点!

我在 github https://github.com/mrdoob/three.js/pull/20732 上的拉取请求评论中包含了这个建议,但我也会在这里添加它,以防有人在搜索时只发现这个 Stack Overflow 问题。

我 运行 使用另一个共享 WebGL 上下文的库遇到了类似的问题。我发现另一个库 (imgui-js) 正在将 gl.BLEND 设置为 true,然后随着上述拉取请求的更改,WebGLState.Reset() 现在将 currentBlendingEnabled 设置为 null。

这导致我场景中的很多纹理显示不正确,因为当随后在 WebGLState 上调用 setBlending 时,它假定如果所需的混合方法是 NoBlending 并且 currentBlendingEnabled 为 null,则 gl.BLEND 是已禁用:

function setBlending(blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha) {
    if (blending === NoBlending) {
       if ( currentBlendingEnabled ) {
          disable(3042);

但是,通过重置使每一帧的 currentBlendingEnabled 值清零但不将 gl.BLEND 设置为 false,我相信这个假设不再正确。仔细观察,即使删除了我正在使用的将 gl.BLEND 值设置为 true 的外部库,我发现拉取请求中的更改对场景中的某些纹理产生了负面影响。在我的案例中,我发现更新 setBlending 函数以遵守 NoBlending 请求(包括当 currentBlendingEnabled 为 null 时)似乎已经纠正了这种情况。也许这也适用于您的情况?

function setBlending(blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha) {
    if (blending === NoBlending) {
       if (currentBlendingEnabled !== false) {
          disable(3042);

WebGLState.reset() 的新实现将随 r126 一起提供。它不仅会重置引擎内部状态标志,还会调用 WebGL 命令来重置实际的 WebGL 状态。这种方法应该可以解决报告的问题。

Link 到 GitHub 的 PR:https://github.com/mrdoob/three.js/pull/21281