是否可以在 THREE.js 中启用无限数量的渲染器? [英] Is it possible to enable unbounded number of renderers in THREE.js?

查看:19
本文介绍了是否可以在 THREE.js 中启用无限数量的渲染器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了避免XY问题,让我解释一下我来自哪里.我想使用 THREE.js 绘制大量堆叠在一起的波形,使用相同的时间轴.波形只是三个.Line,我正在通过修改正交相机的视图边界来实现这些波形的缩放/平移/缩放.

我最初的尝试导致我创建了多个具有固定高度的画布元素,相互堆叠,并将一个 THREE.WebGLRenderer 附加到每个画布.这非常有效,直到我尝试将其缩放超过 15 个左右的波形,其中 THREE.js 给了我一个警告活动的 webgl 上下文过多",并开始删除旧的上下文.

我觉得这是一个不错的做法,考虑到这里应用的技术相同:http://threejs.org/examples/#webgl_multiple_canvases_grid

在此示例中,创建了 4 个 WebGLRenderer,每个画布一个.

那么,是否有可能以某种方式覆盖此警告,并创建无限数量的画布元素,每个元素都有自己的渲染器?

旁白:

我已经考虑使用一个场景并在其中相应地定位波形,并使用类似于 http 的方法使用多个摄像机://threejs.org/examples/#webgl_multiple_views.

问题有两方面:

(1) 我失去了对每个波形进行 dom 操作和轻松附加键和鼠标侦听器的能力.

(2) 此解决方案似乎也无法扩展.一旦渲染器的高度超过 6000 像素的高度,它就会开始进入某种类型的损坏状态,部分场景不会出现,其余的内容会被拉伸以进行补偿.

感谢任何可以提供帮助的人!

解决方案

您可以使用一个非滚动的全窗口大小画布,并为您的波形使用占位符 DIV.然后使用 1 个渲染器,每个波形有 1 个场景,并在渲染每个场景之前使用每个 div 的位置调用 renderer.setViewportrenderer.setScissor.

有效地喜欢这个

renderer.setScissorTest(true);场景.forEach(功能(场景){//获取作为我们想要的位置的占位符的元素//绘制场景var viewElement = scene.viewElement;//获取它相对于页面视口的位置var rect = viewElement.getBoundingClientRect();//检查它是否在屏幕外.如果是这样跳过它if ( rect.bottom <0 || rect.top > renderer.domElement.clientHeight ||rect.right <0 ||rect.left >renderer.domElement.clientWidth ) {返回;//它在屏幕外}//设置视口var 宽度 = rect.right - rect.left;var 高度 = rect.bottom - rect.top;var left = rect.left;var top = rect.top;renderer.setViewport( 左、上、宽、高);renderer.setScissor( 左、上、宽、高);camera.aspect = 宽度/高度;相机.updateProjectionMatrix();renderer.render(场景,相机);});renderer.setScissorTest(false);

示例:

var canvas;var 场景 = [],相机,渲染器,emptyScene;在里面();动画();函数初始化(){canvas = document.getElementById("c");相机 = 新三.透视相机(75, 1, 0.1, 100);相机.位置.z = 1.5;var 几何形状 = [new THREE.BoxGeometry( 1, 1, 1 ),new THREE.SphereGeometry( 0.5, 12, 12 ),新的三十二面体几何(0.5),新的 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  renderer.domElement.clientHeight ||rect.right <0 ||rect.left >renderer.domElement.clientWidth ) {返回;//它在屏幕外}//设置视口var 宽度 = rect.right - rect.left;var 高度 = rect.bottom - rect.top;var left = rect.left;var top = rect.top;renderer.setViewport( 左、上、宽、高);renderer.setScissor( 左、上、宽、高);camera.aspect = 宽度/高度;相机.updateProjectionMatrix();renderer.render(场景,相机);});renderer.setScissorTest(false);}函数兰特(最小,最大){如果(最大==未定义){最大值 = 最小值;分钟 = 0;}返回 Math.random() * ( max - min ) + min;}函数 randColor() {var 颜色 = [ rand( 256 ), rand ( 256 ), rand( 256 ) ];颜色[ Math.random() * 3 |0 ] = 255;返回 ( 颜色[0] <<16 ) |( 颜色[1] << 8 ) |( 颜色[2] << 0 ) ;}

* {box-sizing: 边框框;-moz-box-sizing: 边框框;}身体 {颜色:#000;字体系列:等宽;字体大小:13px;背景色:#fff;边距:0;}#内容 {位置:绝对;顶部:0;宽度:100%;z-索引:1;填充:2em;}#C {位置:绝对;左:0;顶部:0;宽度:100%;高度:100%;}.项目清单 {边距:1em;填充:2em;显示:-webkit-flex;显示:弹性;弹性方向:行;-webkit-flex-direction: 行;}.list-item .scene {宽度:200px;高度:200px;弹性:0 0 自动;-webkit-flex:0 0 自动;}.list-item .description {字体系列:无衬线;字体大小:大;填充左:2em;弹性:1 1 自动;-webkit-flex:1 1 自动;}@media only screen and (max-width : 600px) {#内容 {宽度:100%;}.项目清单 {边距:0.5em;填充:0.5em;弹性方向:列;-webkit-flex-direction: 列;}.list-item .description {填充左:0em;}}

<canvas id="c"></canvas><div id="内容">

<script id="template" type="notjs"><div class="scene"></div><div class="description">一些关于这个对象、场景等的随机文本</div><script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script>

更新:

此处的原始解决方案使用带有 position: fixed 的画布,表示画布不滚动.下面的新解决方案将其更改为 position: absolute;top: 0 然后每帧设置画布的变换

 canvas.style.transform = `translateY(${window.scrollY}px`;

这样做的好处是,即使我们不能在每一帧都更新画布,画布也会随着页面滚动,直到我们有机会更新它.这使得滚动保持同步.

您可以将旧解决方案新解决方案.两者都设置为仅每 4 帧渲染一次以夸大问题.上下滚动它们,区别应该很明显.

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>

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.

这篇关于是否可以在 THREE.js 中启用无限数量的渲染器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆