【问题标题】:Display multiple instances of three.js in a single page在一个页面中显示多个three.js的实例
【发布时间】:2016-03-01 18:47:32
【问题描述】:

我有一个网络应用程序,我需要在不同的容器中显示多个 3D 对象。目前,我一直在实例化多个 three.js 渲染器,每个容器一个。但是,我收到此错误消息:“警告:活动的 WebGL 上下文过多。最旧的上下文将丢失”,并且在 ~10 个渲染器之后我无法再打开。这是一个例子

http://brainspell.org/article/24996404

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

谢谢!

【问题讨论】:

标签: javascript three.js webgl


【解决方案1】:

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

There's an example here.

这是 StackOverflow 中该示例的答案

https://stackoverflow.com/a/30633132/128511


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

例子:

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 来启动/停止屏幕外的任何画布更新。

【讨论】:

  • 您使用了“最简单的方法”一词。有没有更复杂的方法可以工作?对于我正在构建的应用程序,Three.js 实例是模块化组件的一部分,用户可以自行决定在页面中添加或删除该组件。所以在这种情况下,一个three.js 实例不知道其他three.js 实例并独立运行是有意义的。对此有什么想法吗?
  • 浏览器限制了您可以在页面上显示的 webgl 上下文的数量。通常只有 8 个。我所知道的绕过该限制的唯一方法是通过 virtualizing webgl,但对于 WebGL2,这并非易事。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-22
  • 1970-01-01
  • 2022-07-21
  • 1970-01-01
  • 1970-01-01
  • 2017-01-31
  • 1970-01-01
相关资源
最近更新 更多