unconed / threestrap

Minimal Three.js Bootstrapper
Other
238 stars 20 forks source link

Multiple Renderers #1

Closed speigg closed 10 years ago

speigg commented 10 years ago

It would be great if there were an easy way to specify multiple renderers and their stacking order :)

For example: CSS3DRenderer on top of a WebGLRenderer.

Thanks for creating this, it's very useful already!

unconed commented 10 years ago

The goal of Threestrap is to wrap existing functionality as simply as possible. Supporting multiple renderers directly is tricky. Just the fact that there are multiple DOM elements kind of throws it off, as e.g. the resize plug-in would now have to resize two or more divs. The cameracontrols plugin might have to bind the events twice, etc.

It would be much simpler if we just had a THREE.MultiRenderer. It could spawn the sub-renderers and coordinate them. You could wrap e.g. two canvases or a canvas + a css3D container. It would act as normal THREE renderer and expose a single DOM element.

If it existed, Threestrap could use it already. You could specify:

renderer: {
  klass: THREE.MultiRenderer,
  parameters: [ new THREE.WebGLRenderer(options), new THREE.CSS3DRenderer(options) ]
}

I could also add some syntactic sugar so you can just do:

renderer: {
  klass: [ THREE.WebGLRenderer, THREE.CSS3DRenderer ]
}

But it is really up to Three.js to support it first. Otherwise Threestrap becomes a framework of hacks instead of wrappers.

speigg commented 10 years ago

Thanks for your quick response! I think it's just a matter of separating the concerns of each plugin a bit more cleanly.

  1. the size plugin currently calculates the renderer's size, applies the size to the renderer's element, and emits a resize event. It seems to me that it should be simple to refactor this so that the size plugin knows nothing about the three.renderer, and simply emits the appropriate resize event, which the renderer plugin can then listen to and respond to. This way, the size plugin only needs to know about changes to the container's size (three.element), and nothing else, and the renderer resizing logic is contained within the renderer plugin.
  2. The render plugin can similarly be folded into the renderer plugin, by having the renderer plugin listen to the render event and render all of the renderers.

I think that basically it.... do you think this makes sense?

speigg commented 10 years ago

Oh, and about the camera controls, can't the events be bound to the three.element container?

speigg commented 10 years ago

I just checked some of the three.js examples, and they do exactly that: bind the controls to the container, and not the renderer's element.

unconed commented 10 years ago

The renderer could indeed listen for a resize event, this is a minor code change. But regardless, many three.js components need a tight coupling to the renderer, it cannot all be abstracted through events (again: keep it simple). So replacing one renderer with many is still a bad idea.

As for the render plugin, it should be separate, because you may replace it with e.g. a multi-pass effects composer. The core set up of rendering the scene directly to the screen with the default camera is just a sane default.

Re: camera controls. I bind it to the domElement because it may be positioned with CSS. This is how fixed aspect ratios are implemented, with "black bars" that are just margins. It also makes threestrap more agnostic with regards to custom CSS.

unconed commented 10 years ago

By the way, you may be able to just do this by creating one ["core"] threestrap and another that's ["empty", "render"]. Then you manually set three.scene and three.camera on the second one.

speigg commented 10 years ago

Okay. Thanks for the feedback. I may go the route of implementing my own MultiRenderer as you originally suggested.

unconed commented 10 years ago

I just tried the two threestraps approach, works fine:

https://github.com/unconed/threestrap/blob/master/examples/multiple_renderers.html

This is two separate scenes, but you can link the scenes too.

speigg commented 10 years ago

Interesting. Thanks for the example!

speigg commented 10 years ago

So I spent last night creating a very simple MultiRenderer that integrates nicely (I think) with threestrap & three.js renderers.

https://gist.github.com/speigg/6af4527aac8c10d2e46a

These is how I use it in threestrap:

  options.renderer = {
    klass: MultiRenderer,
    parameters: {
      renderers: [THREE.WebGLRenderer, THREE.CSS3DRenderer], // stacked back to front
      parameters: [
        {
          alpha: true,
          depth: true,
          stencil: true,
          preserveDrawingBuffer: true,
          antialias: true
        },
        {} // CSS3DRenderer doesn't have any parameters
      ]
    }
  }

This works out of the box with almost all threestrap capabilites. In order to support the size plugin's renderWidth and renderHeight features, the following can be added (somewhere):

three.on('resize', function (event, three) {
    var w = event.viewWidth
    var h = event.viewHeight
    var rw = event.renderWidth
    var rh = event.renderHeight
    // The MultiRenderer can also accept an additional two arguments in setSize
    // Any other renderers will ignore the additional arguments. 
    three.renderer.setSize(w, h, rw, rh)
});

Feel free to do what you want with this. If it were integrated into threestrap it would work even better, since rw and rh can be passed directly as 3rd and 4th parameters to three.renderer.setSize in the size plugin.

unconed commented 10 years ago

I had the same idea yesterday, of passing in rw/rh. But WebGLRenderer and CanvasRenderer already have a 3rd parameter (updateStyle). I'd prefer to not unofficially change the signature of that API, this is again something that three.js needs to figure out first, especially in light of retina / high-DPI.

The use case of having render width / render height differ from view width / height was included mainly for high-end demos that would otherwise max out your GPU. I'm not sure if it makes sense to apply it to non-canvas renderers... you would have to set css zoom or something, and it wouldn't lower the resolution, just make some of the text look worse.

speigg commented 10 years ago

I missed that updateStyle parameter, thanks. Will have to tweak this. Perhaps I'll add a setRenderSize function.

If you look at the gist, the MultiRenderer is only using the render width / render height on canvas-based renderers, as you say.

speigg commented 10 years ago

I updated the gist with the setRenderSize approach. This seems cleaner anyways. Only requires adding the following to get everything working:

three.on('resize', function (event, three) {
    if (three.renderer.setRenderSize)
      three.renderer.setRenderSize(event.renderWidth, event.renderHeight)
});

Seems to work well in a quick test I made.

speigg commented 10 years ago

Btw, I suggest renaming the capWidth and capHeight options to capRenderWidth and capRenderHeight for clarity.

speigg commented 10 years ago

I just tested everything (scale/capWidth/capHeight), works great. Thanks for the feedback! And aside from high-end demos, I think threestrap's render width / render height feature is also very useful for low end systems (mobile devices).

unconed commented 10 years ago

I renamed them to maxRenderWidth / maxRenderHeight and moved the setSize call to renderer. I also added a check for a setRenderSize call. This is called before setSize for non-canvas based renderers that support it (like multirenderer).

It's sitting in master right now, 0.0.8-dev. If you could check to make sure it all works right with multi renderer, I can tag the release since there's a few other minor tweaks that can go out.

unconed commented 10 years ago

If you want to make an example for multi renderer with webgl + css3d, that would be very welcome by the way.

speigg commented 10 years ago

Awesome. I'll test it out today. Did you want to include MultiRenderer in this repository? I suppose it's needed for the example. I'll submit it to the three.js repository as well, and if they do end up including it then threestrap can remove it later.

speigg commented 10 years ago

All done, in #2 :)

I placed MultiRenderer and CSS3DRenderer in a vendor/renderers directory.

I realized that even if three.js puts MultiRenderer in their repository, it would probably go in their examples directory, so threestrap can probably just keep it in vendor like the controls classes.