I have a web app where I need to display multiple 3D objects in different containers. For the moment, I have been instantiating multiple three.js renderers, one per container. However, I get this error message: "WARNING: Too many active WebGL contexts. Oldest context will be lost" and after ~10 renderers I can't open any more. Here's an example
http://brainspell.org/article/24996404
How should I go to have multiple three.js containers in a single web page? Is it possible to have a single renderer with multiple scenes and draw each scene in a different container (a different renderer.domElement) ?
Thank you!
This has been covered elsewhere but the easiest way is to just use one instance of three.js, make it cover the entire window, put place holder divs where you want to draw things, and then use element.getClientBoundingRect
to set the scissor and viewport for each scene you want to draw in each element
There's an example here.
Here's the answer in StackOverflow from which that sample originates
https://stackoverflow.com/a/30633132/128511
Another solution would be to use another not visible canvas and pass it to all instances of three.js (which will mean they all use the same WebGL context), then copy the results to individual 2D canvas.
Example:
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>
The copying might make it slow. Ideally you'd use an IntersectionObserver
to start/stop any canvas offscreen from updating.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With