在单个页面中显示 three.js 的多个实例

Display multiple instances of three.js in a single page

我有一个 Web 应用程序,我需要在其中显示不同容器中的多个 3D 对象。目前,我一直在实例化多个 three.js 渲染器,每个容器一个。但是,我收到此错误消息:"WARNING: Too many active WebGL contexts. Oldest context will be lost" 并且在 ~10 个渲染器之后我无法再打开。这是一个例子

http://brainspell.org/article/24996404

我应该如何在一个网页中拥有多个 three.js 容器?是否可以使用具有多个场景的单个渲染器并将每个场景绘制在不同的容器中(不同的renderer.domElement)?

谢谢!

这已在其他地方介绍过,但最简单的方法是只使用 three.js 的一个实例,使其覆盖整个 window,将占位符 div 放在你想要绘制的地方,然后然后使用element.getClientBoundingRect为每个元素中要绘制的每个场景设置剪刀和视口

There's an example here.

这是 Whosebug 中的答案,该示例源自该答案


另一种解决方案是使用另一个不可见的 canvas 并将其传递给 three.js 的所有实例(这意味着它们都使用相同的 WebGL 上下文),然后将结果复制到个人2D canvas.

示例:

canvas { width: 128px; height: 128px; display: block; }
.outer { border: 1px solid black; margin: 5px; display: inline-block; }
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r132/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/controls/OrbitControls.js';

function main(visibleCanvas, offscreenSharedCanvas) {
  const visibleCanvas2DContext = visibleCanvas.getContext('2d');
  const renderer = new THREE.WebGLRenderer({canvas: offscreenSharedCanvas});

  const fov = 45;
  const aspect = 2;  // the canvas default
  const near = 0.1;
  const far = 100;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.set(0, 10, 20);

  const controls = new OrbitControls(camera, visibleCanvas);
  controls.target.set(0, 5, 0);
  controls.update();

  const scene = new THREE.Scene();
  scene.background = new THREE.Color('black');

  {
    const planeSize = 40;

    const loader = new THREE.TextureLoader();
    const texture = loader.load('https://threejsfundamentals.org/threejs/resources/images/checker.png');
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    texture.magFilter = THREE.NearestFilter;
    const repeats = planeSize / 2;
    texture.repeat.set(repeats, repeats);

    const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
    const planeMat = new THREE.MeshPhongMaterial({
      map: texture,
      side: THREE.DoubleSide,
    });
    const mesh = new THREE.Mesh(planeGeo, planeMat);
    mesh.rotation.x = Math.PI * -.5;
    scene.add(mesh);
  }
  {
    const cubeSize = 4;
    const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
    const cubeMat = new THREE.MeshPhongMaterial({color: '#8AC'});
    const mesh = new THREE.Mesh(cubeGeo, cubeMat);
    mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
    scene.add(mesh);
  }
  {
    const sphereRadius = 3;
    const sphereWidthDivisions = 32;
    const sphereHeightDivisions = 16;
    const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
    const sphereMat = new THREE.MeshPhongMaterial({color: '#CA8'});
    const mesh = new THREE.Mesh(sphereGeo, sphereMat);
    mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
    scene.add(mesh);
  }

  {
    const color = 0xFFFFFF;
    const intensity = 1;
    const light = new THREE.DirectionalLight(color, intensity);
    light.position.set(0, 10, 0);
    light.target.position.set(-5, 0, 0);
    scene.add(light);
    scene.add(light.target);
  }

  // need to resize both the visibleCanvas and the offscreenSharedCanvas
  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = visibleCanvas.clientWidth;
    const height = visibleCanvas.clientHeight;
    const needResize = offscreenSharedCanvas.width !== width || offscreenSharedCanvas.height !== height ||
                       visibleCanvas.width !== width || visibleCanvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
      visibleCanvas.width = width;
      visibleCanvas.height = height;
    }
    return needResize;
  }

  let requestId;
  let visible;
  function render() {
    requestId = undefined;

    if (resizeRendererToDisplaySize(renderer)) {
      camera.aspect = visibleCanvas.clientWidth / visibleCanvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    renderer.render(scene, camera);
    
    // copy the offscreenSharedCanvas to the visible canvas
    visibleCanvas2DContext.drawImage(offscreenSharedCanvas, 0, 0);

    if (visible) {
      startRendering();
    }
  }
  
  function startRendering() {
    if (!requestId) {
      requestId = requestAnimationFrame(render);
    }
  }

  // use an intersection observer to only render this canvas when on screen
  const intersectionObserver = new IntersectionObserver(entries => {
    if (entries[0].isIntersecting) {
      visible = true;
      startRendering();
    } else {
      visible = false;
    }
  });
  intersectionObserver.observe(visibleCanvas);
}

const offscreenSharedCanvas = document.createElement('canvas');
for (let i = 0; i < 20; ++i) {
  const outer = document.createElement('div');
  outer.className = 'outer';
  document.body.appendChild(outer);
  const canvas = document.createElement('canvas');
  outer.appendChild(canvas);
  main(canvas, offscreenSharedCanvas);
}

</script>

复制可能会变慢。理想情况下,您可以使用 IntersectionObserver 到 start/stop 任何 canvas 屏幕外的更新。