Is it possible to enable unbounded number of renderers in THREE.js?

前端 未结 1 470
再見小時候
再見小時候 2020-11-27 22:47

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 a

相关标签:
1条回答
  • 2020-11-27 23:07

    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>

    Update:

    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.

    0 讨论(0)
提交回复
热议问题