Peque
Peque

Reputation: 14821

Multiple scenes interleaved with text

I was considering using three.js for my 3D model visualization Sphinx extension. The extension allows you to easily visualize interactive 3D objects in your documentation, but it currently uses an oudated and unmaintained library for that.

The three.js library has a great collection of examples. Among them, there is one that caught my attention: the WebGL multiple elements text example.

It is a great example because it fits my needs for the Sphinx extension:

Now, it has one single caveat: CPU/GPU consumption. Even when you are not scrolling nor interacting with the 3D visualizations, CPU/GPU seem to be busy.

At first I thought it could be due to the animations in each of the scenes. I do not need animations for my extension, only interactions, so I decided to remove them and force re-rendering on scene user interaction only:

diff --git a/examples/webgl_multiple_elements_text.html b/examples/webgl_multiple_elements_text.html
index 289e4935a..e9fe7e087 100644
--- a/examples/webgl_multiple_elements_text.html
+++ b/examples/webgl_multiple_elements_text.html
@@ -196,6 +196,7 @@
                    scene.userData.camera = camera;

                    var controls = new THREE.OrbitControls( camera, views[ n ] );
+                   controls.addEventListener( 'change', render );
                    scene.userData.controls = controls;

                    scenes.push( scene );
@@ -223,7 +224,6 @@
            function animate() {

                render();
-               requestAnimationFrame( animate );

            }

With that very simple change:

There is, however, one problem now: if you try to scroll down the page, scenes will not scroll with the text.

How could I fix that?

Considerations:

Upvotes: 1

Views: 87

Answers (1)

user128511
user128511

Reputation:

Add a queue to call render in the scroll event

window.addEventListerner('scroll', queueRenderIfNotQueued);

let renderQueued = false;
function render() {
  renderQueued = false;
  ...
}

function queueRenderIfNotQueued() {
  if (!renderQueued) {
    renderQueued = true;
    requestAnimationFrame(render);
  }
}

You probably also want to render on resize

window.addEventListerner('resize', queueRenderIfNotQueued);

Here's an example modified from ideas from this page and this page

'use strict';

/* global THREE */

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    alpha: true
  });

  const sceneElements = [];

  function addScene(elem, fn) {
    sceneElements.push({
      elem,
      fn
    });
  }

  function makeScene(elem) {
    const scene = new THREE.Scene();

    const fov = 45;
    const aspect = 2; // the canvas default
    const near = 0.1;
    const far = 5;
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.set(0, 1, 2);
    camera.lookAt(0, 0, 0);

    const controls = new THREE.OrbitControls(camera, elem);
    controls.enableZoom = false;
    controls.enablePan = false;

    {
      const color = 0xFFFFFF;
      const intensity = 1;
      const light = new THREE.DirectionalLight(color, intensity);
      light.position.set(-1, 2, 4);
      scene.add(light);
    }

    const geometry = new THREE.BoxBufferGeometry(1, 1, 1);
    const material = new THREE.MeshPhongMaterial({color:'red'});
    const mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
    
    function render(rect) {
      camera.aspect = rect.width / rect.height;
      camera.updateProjectionMatrix();
      renderer.render(scene, camera);    
    }
    
    function renderWithBounds() {
      const rect = elem.getBoundingClientRect();
      const {
        left,
        right,
        top,
        bottom,
        width,
        height
      } = rect;

      renderer.setViewport(left, top, width, height);
      renderer.setScissor(left, top, width, height);
      render(rect);
    }
    
    controls.addEventListener('change', renderWithBounds);

    addScene(elem, render);
  }

  [...document.querySelectorAll('.diagram')].forEach(makeScene);
 

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  const clearColor = new THREE.Color('#000');

  let renderQueued = false;
  function render() {
    renderQueued = false;
    resizeRendererToDisplaySize(renderer);

    renderer.setScissorTest(false);
    renderer.setClearColor(clearColor, 0);
    renderer.clear(true, true);
    renderer.setScissorTest(true);

    const transform = `translateY(${window.scrollY}px)`;
    renderer.domElement.style.transform = transform;

    for (const {
        elem,
        fn
      } of sceneElements) {
      // get the viewport relative position opf this element
      const rect = elem.getBoundingClientRect();
      const {
        left,
        right,
        top,
        bottom,
        width,
        height
      } = rect;

      const isOffscreen =
        bottom < 0 ||
        top > renderer.domElement.clientHeight ||
        right < 0 ||
        left > renderer.domElement.clientWidth;

      if (!isOffscreen) {
        renderer.setViewport(left, top, width, height);
        renderer.setScissor(left, top, width, height);

        fn(rect);
      }
    }
  }

  function queueRenderIfNotQueued() {
    if (!renderQueued) {
      renderQueued = true;
      requestAnimationFrame(render);
    }
  }
  
  queueRenderIfNotQueued();
  window.addEventListener('scroll', queueRenderIfNotQueued);
  window.addEventListener('resize', queueRenderIfNotQueued);
}

main();
#c {
  position: absolute;
  left: 0;
  top: 0;
  width: 100vw;
  height: 100vh;
  display: block;
  z-index: -1;
}

.diagram {
  display: inline-block;
  width: 5em;
  height: 3em;
}

.left {
  float: left;
  margin-right: .25em;
}

.right {
  float: right;
  margin-left: .25em;
}

p {
  margin: 1em auto;
  max-width: 500px;
  font-size: xx-large;
}
<canvas id="c"></canvas>
<p>
  <span class="diagram left"></span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mi turpis, pellentesque sed aliquam vel, tincidunt eget massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
<p>
  <span class="diagram right"></span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mi turpis, pellentesque sed aliquam vel, tincidunt eget massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
<p>
  <span class="diagram left"></span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mi turpis, pellentesque sed aliquam vel, tincidunt eget massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
<p>
  <span class="diagram right"></span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mi turpis, pellentesque sed aliquam vel, tincidunt eget massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
<p>
  <span class="diagram left"></span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mi turpis, pellentesque sed aliquam vel, tincidunt eget massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
<p>
  <span class="diagram right"></span>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mi turpis, pellentesque sed aliquam vel, tincidunt eget massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>

<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/three.min.js"></script>
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r98/js/controls/OrbitControls.js"></script>

Upvotes: 1

Related Questions