Saturday, January 19, 2013

Can't Render a Three.js Scene in Two Places

‹prev | My Chain | next›

I am done with the Moon simulator chapter in 3D Game Programming for Kids and am ready to move onto other things (like more actual games). It occurs to me, however, that I do not know how to allow two cameras to be active at the same time in Three.js. I was able to do this king of thing in CubicVR.js / Gladius, but am not even sure it is possible in Three.js.

In the chapter, I include some side-by-side comparisons of the Moon's position and its phase:

Those screenshots are labor intensive—I need to screen capture each (and resize the print scale of each). This would not be something that I would have the kids do, but it would be great to have the Moon's phase superimposed on the overhead shot.

Unfortunately, as best I can tell, there is no way to accomplish this in Three.js. The render that spits the scene out onto canvas, takes only one camera:
  function animate() {
    renderer.render(scene, camera);
Three.js does include a CombinedCamera. Sadly this does not do what I hope—it just makes it easy to switch back and forth between a perspective camera and an orthographic camera.

I might not be entirely out of luck though. Just because Three.js renders are restricted to a single camera does not necessarily mean that I am restricted to a single renderer.

So I add a "picture-in-picture" element to the screen:
  var pip = document.createElement('div'); = 250; = 250; = 'absolute'; = 'black'; = "5px"; = '2px solid white'; = "0px 20px"; = "50px"; = "25px";
Then I create a second renderer and add its domElement to the picture-in-picture:
  var renderer2 = new THREE.WebGLRenderer();
  renderer2.setSize(250, 250);
This is just like the regular renderer, except that I append the renderer's DOM element to a specific element instead of appending it to the document body. I also note that the aspect ratio that I will have is 1:1, so I redefine the "Earth Cam" to have a 1:1 aspect ratio:
  //var earth_cam = new THREE.PerspectiveCamera(45, aspect_ratio, 1, 1e6);
  var earth_cam = new THREE.PerspectiveCamera(45, 1, 1, 1e6);
With that, I am ready to try double rendering in Three.js. I add the second renderer to my animate() method:
  function animate() {
    renderer2.render(scene, earth_cam);
    renderer.render(scene, camera);
    // ...
Unfortunately, that does not work. I only get the new camera, with the rest of the scene completely blank:

In addition to the visual evidence, Three.js spits out a lot of warnings telling me that I am doing something very wrong:
THREE.WebGLRenderer 52 Three.js:15545
THREE.WebGLRenderer 52 Three.js:15545
WebGL: INVALID_OPERATION: useProgram: object not from this context Three.js:20282
WebGL: INVALID_OPERATION: uniformMatrix4fv: location is not from current program Three.js:20298
WebGL: INVALID_OPERATION: uniform3f: location not for current program Three.js:20743
WebGL: INVALID_OPERATION: uniform1f: location not for current program
I try moving the second renderer out into its own animate2() function:
  function animate2() {
    renderer2.render(scene, earth_cam);
But this has no effect. I still only see a single camera's output and get lots of warnings in the JavaScript console. I try rendering the second camera just a single time. I also try switching the renderer to a CanvasRendered. All have no effect.

It seems that I simply cannot render the same scene to two different renderers. If anyone has any thoughts, the non-worky code is up. In the meantime, I will sleep on it and perhaps revisit tomorrow.

Day #635


  1. Is this what you were looking for?

    1. Yup, that would have really helped at the time :)

      I did manage to hack together a more or less workable solution:

      But that example is better.