In order to avoid the XY problem, let me explain where I'm coming from. I would like to plot a large number of waveforms stacked on top of each other using the same time axis, using THREE.js. The waveforms are simply THREE.Line's and I am implementing zoom/pan/scaling of these waveforms by modifying the view bounds of an Orthographic camera.
My initial attempt at accomplishing this lead me to create multiple canvas elements with fixed height, stacked on top of each other, and attach a THREE.WebGLRenderer to each canvas. This worked perfectly, until I tried scaling it past 15 or so waveforms, where THREE.js gave me a warning "too many active webgl contexts", and started deleting old contexts.
I feel like this is decent practice, considering it's the same technique applied here: http://threejs.org/examples/#webgl_multiple_canvases_grid
In this example, 4 WebGLRenderers are created, one for each canvas.
So, is it possible to override this warning somehow, and create an unbounded number of canvas elements, each with their own renderer?
ASIDE:
I have considered using one scene and positioning waveforms accordingly within it, and using multiple cameras with an approach similar to http://threejs.org/examples/#webgl_multiple_views.
The problems are two-fold:
(1) I lose the ability to dom-manipulate and easily attach key and mouse listeners on a per-waveform basis.
(2) This solution doesn't seem to scale either. Once the renderer's height passes somewhere around 6000px height, it starts to enter some type of corrupt state and part of the scene doesn't appear, with the rest of the content appearing stretched to compensate.
Thanks to anyone who can help!
You can use one non-scrolling full window size canvas, and place holders DIVs for your wave forms. Then with 1 renderer have 1 scene per waveform and call renderer.setViewport
and renderer.setScissor
with the location of each div before rendering each scene.
Effectively like this
renderer.setScissorTest( true );
scenes.forEach( function( scene ) {
// get the element that is a place holder for where we want to
// draw the scene
var viewElement = scene.viewElement;
// get its position relative to the page's viewport
var rect = viewElement.getBoundingClientRect();
// check if it's offscreen. If so skip it
if ( rect.bottom < 0 || rect.top > renderer.domElement.clientHeight ||
rect.right < 0 || rect.left > renderer.domElement.clientWidth ) {
return; // it's off screen
}
// set the viewport
var width = rect.right - rect.left;
var height = rect.bottom - rect.top;
var left = rect.left;
var top = rect.top;
renderer.setViewport( left, top, width, height );
renderer.setScissor( left, top, width, height );
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.render( scene, camera );
} );
renderer.setScissorTest( false );
Example:
var canvas;
var scenes = [], camera, renderer, emptyScene;
init();
animate();
function init() {
canvas = document.getElementById( "c" );
camera = new THREE.PerspectiveCamera( 75, 1, 0.1, 100 );
camera.position.z = 1.5;
var geometries = [
new THREE.BoxGeometry( 1, 1, 1 ),
new THREE.SphereGeometry( 0.5, 12, 12 ),
new THREE.DodecahedronGeometry( 0.5 ),
new THREE.CylinderGeometry( 0.5, 0.5, 1, 12 ),
];
var template = document.getElementById("template").text;
var content = document.getElementById("content");
var emptyScene = new THREE.Scene();
var numScenes = 100;
for ( var ii = 0; ii < numScenes; ++ii ) {
var scene = new THREE.Scene();
// make a list item.
var element = document.createElement( "div" );
element.innerHTML = template;
element.className = "list-item";
// Look up the element that represents the area
// we want to render the scene
scene.element = element.querySelector(".scene");
content.appendChild(element);
// add one random mesh to each scene
var geometry = geometries[ geometries.length * Math.random() | 0 ];
var material = new THREE.MeshLambertMaterial( { color: randColor() } );
scene.add( new THREE.Mesh( geometry, material ) );
light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 0.5, 0.8, 1 );
scene.add( light );
light = new THREE.DirectionalLight( 0xffffff );
light.position.set( -0.5, -0.8, -1 );
scene.add( light );
scenes.push( scene );
}
renderer = new THREE.WebGLRenderer( { canvas: canvas, antialias: true } );
renderer.setClearColor( 0xFFFFFF );
}
function updateSize() {
var width = canvas.clientWidth;
var height = canvas.clientHeight;
if ( canvas.width !== width || canvas.height != height ) {
renderer.setSize ( width, height, false );
}
}
function animate() {
render();
requestAnimationFrame( animate );
}
function render() {
updateSize();
canvas.style.transform = `translateY(${window.scrollY}px`;
renderer.setClearColor( 0xFFFFFF );
renderer.clear( true );
renderer.setClearColor( 0xE0E0E0 );
renderer.setScissorTest( true );
scenes.forEach( function( scene ) {
// so something moves
scene.children[0].rotation.x = Date.now() * 0.00111;
scene.children[0].rotation.z = Date.now() * 0.001;
// get the element that is a place holder for where we want to
// draw the scene
var element = scene.element;
// get its position relative to the page's viewport
var rect = element.getBoundingClientRect();
// check if it's offscreen. If so skip it
if ( rect.bottom < 0 || rect.top > renderer.domElement.clientHeight ||
rect.right < 0 || rect.left > renderer.domElement.clientWidth ) {
return; // it's off screen
}
// set the viewport
var width = rect.right - rect.left;
var height = rect.bottom - rect.top;
var left = rect.left;
var top = rect.top;
renderer.setViewport( left, top, width, height );
renderer.setScissor( left, top, width, height );
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.render( scene, camera );
} );
renderer.setScissorTest( false );
}
function rand( min, max ) {
if ( max == undefined ) {
max = min;
min = 0;
}
return Math.random() * ( max - min ) + min;
}
function randColor() {
var colors = [ rand( 256 ), rand ( 256 ), rand( 256 ) ];
colors[ Math.random() * 3 | 0 ] = 255;
return ( colors[0] << 16 ) |
( colors[1] << 8 ) |
( colors[2] << 0 ) ;
}
* {
box-sizing: border-box;
-moz-box-sizing: border-box;
}
body {
color: #000;
font-family:Monospace;
font-size:13px;
background-color: #fff;
margin: 0;
}
#content {
position: absolute;
top: 0; width: 100%;
z-index: 1;
padding: 2em;
}
#c {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.list-item {
margin: 1em;
padding: 2em;
display: -webkit-flex;
display: flex;
flex-direction: row;
-webkit-flex-direction: row;
}
.list-item .scene {
width: 200px;
height: 200px;
flex: 0 0 auto;
-webkit-flex: 0 0 auto;
}
.list-item .description {
font-family: sans-serif;
font-size: large;
padding-left: 2em;
flex: 1 1 auto;
-webkit-flex: 1 1 auto;
}
@media only screen and (max-width : 600px) {
#content {
width: 100%;
}
.list-item {
margin: 0.5em;
padding: 0.5em;
flex-direction: column;
-webkit-flex-direction: column;
}
.list-item .description {
padding-left: 0em;
}
}
<canvas id="c"></canvas>
<div id="content">
</div>
<script id="template" type="notjs">
<div class="scene"></div>
<div class="description">some random text about this object, scene, whatever</div>
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script>
The original solution here used a canvas with position: fixed
meaning the canvas did not scroll. The new solution below changes it to position: absolute; top: 0
and then sets the canvas's transform every frame
canvas.style.transform = `translateY(${window.scrollY}px`;
This has the advantage that even if we can't update the canvas every frame the canvas will scroll with the page until we get a chance to update it. This makes the scrolling stay in sync.
You can compare the old solution to the new solution. Both are set to only render every 4th frame to exaggerate the issue. Scroll them up and down and the difference should be clear.
Yet another solution is to virtualize the WebGL context in which you create one offscreen WebGL context and then patch things so other other uses of WebGL get a virtual WebGL context that is simulated on top of a single shared actual WebGL context.
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