是否可以在 THREE.js 中启用无限数量的渲染器?
Is it possible to enable unbounded number of renderers in THREE.js?
为了避免XY问题,让我解释一下我的来历。
我想使用相同的时间轴绘制大量堆叠在一起的波形,使用 THREE.js。波形只是 THREE.Line,我正在通过修改正交相机的视图边界来实现这些波形的 zoom/pan/scaling。
我最初尝试完成此操作导致我创建多个具有固定高度的 canvas 元素,彼此堆叠,并在每个 canvas 上附加一个 THREE.WebGLRenderer。
这非常有效,直到我尝试将它缩放到超过 15 个左右的波形,其中 THREE.js 给了我一个警告 "too many active webgl contexts",并开始删除旧的上下文。
我觉得这是一种不错的做法,考虑到它与此处应用的技术相同:http://threejs.org/examples/#webgl_multiple_canvases_grid
在此示例中,创建了 4 个 WebGLRenderer,每个 canvas。
那么,是否有可能以某种方式覆盖此警告,并创建无限数量的 canvas 元素,每个元素都有自己的渲染器?
旁白:
我考虑过使用一个场景并在其中相应地定位波形,并使用类似于 http://threejs.org/examples/#webgl_multiple_views 的方法使用多个摄像机。
问题有两个:
(1) 我失去了 dom 在每个波形的基础上操纵和轻松附加键和鼠标侦听器的能力。
(2) 该解决方案似乎也无法扩展。一旦渲染器的高度超过 6000 像素左右,它就会开始进入某种损坏状态,部分场景不会出现,其余内容会被拉伸以进行补偿。
感谢任何能提供帮助的人!
您可以使用一个非滚动完整 window 大小 canvas,并为您的波形占位符 DIV。然后使用 1 个渲染器,每个波形有 1 个场景,并在渲染每个场景之前使用每个 div 的位置调用 renderer.setViewport
和 renderer.setScissor
。
效果像这样
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 );
示例:
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>
更新:
此处的原始解决方案使用 canvas 和 position: fixed
,这意味着 canvas 没有滚动。下面的新解决方案将其更改为 position: absolute; top: 0
,然后设置 canvas 的每帧变换
canvas.style.transform = `translateY(${window.scrollY}px`;
这样做的好处是,即使我们不能更新 canvas 每一帧,canvas 也会随着页面滚动,直到我们有机会更新它。这使滚动保持同步。
你可以比较old solution to the new solution。两者都设置为仅每 4 帧渲染一次以夸大问题。上下滚动它们,区别应该很明显。
更新 2:
另一种解决方案是 virtualize the WebGL context,您可以在其中创建一个屏幕外 WebGL 上下文,然后进行修补,以便 WebGL 的其他其他用途获得一个虚拟 WebGL 上下文,该上下文是在单个共享的实际 WebGL 上下文之上模拟的.
为了避免XY问题,让我解释一下我的来历。 我想使用相同的时间轴绘制大量堆叠在一起的波形,使用 THREE.js。波形只是 THREE.Line,我正在通过修改正交相机的视图边界来实现这些波形的 zoom/pan/scaling。
我最初尝试完成此操作导致我创建多个具有固定高度的 canvas 元素,彼此堆叠,并在每个 canvas 上附加一个 THREE.WebGLRenderer。 这非常有效,直到我尝试将它缩放到超过 15 个左右的波形,其中 THREE.js 给了我一个警告 "too many active webgl contexts",并开始删除旧的上下文。
我觉得这是一种不错的做法,考虑到它与此处应用的技术相同:http://threejs.org/examples/#webgl_multiple_canvases_grid
在此示例中,创建了 4 个 WebGLRenderer,每个 canvas。
那么,是否有可能以某种方式覆盖此警告,并创建无限数量的 canvas 元素,每个元素都有自己的渲染器?
旁白:
我考虑过使用一个场景并在其中相应地定位波形,并使用类似于 http://threejs.org/examples/#webgl_multiple_views 的方法使用多个摄像机。
问题有两个:
(1) 我失去了 dom 在每个波形的基础上操纵和轻松附加键和鼠标侦听器的能力。
(2) 该解决方案似乎也无法扩展。一旦渲染器的高度超过 6000 像素左右,它就会开始进入某种损坏状态,部分场景不会出现,其余内容会被拉伸以进行补偿。
感谢任何能提供帮助的人!
您可以使用一个非滚动完整 window 大小 canvas,并为您的波形占位符 DIV。然后使用 1 个渲染器,每个波形有 1 个场景,并在渲染每个场景之前使用每个 div 的位置调用 renderer.setViewport
和 renderer.setScissor
。
效果像这样
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 );
示例:
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>
更新:
此处的原始解决方案使用 canvas 和 position: fixed
,这意味着 canvas 没有滚动。下面的新解决方案将其更改为 position: absolute; top: 0
,然后设置 canvas 的每帧变换
canvas.style.transform = `translateY(${window.scrollY}px`;
这样做的好处是,即使我们不能更新 canvas 每一帧,canvas 也会随着页面滚动,直到我们有机会更新它。这使滚动保持同步。
你可以比较old solution to the new solution。两者都设置为仅每 4 帧渲染一次以夸大问题。上下滚动它们,区别应该很明显。
更新 2:
另一种解决方案是 virtualize the WebGL context,您可以在其中创建一个屏幕外 WebGL 上下文,然后进行修补,以便 WebGL 的其他其他用途获得一个虚拟 WebGL 上下文,该上下文是在单个共享的实际 WebGL 上下文之上模拟的.