I'm just curious but have you considered making CSS based sizing the default? If you did less code would have to change for various use cases
For example many samples do stuff like this
camera = new THREE.PerspectiveCamera( 
    50, window.innerWidth / window.innerHeight, 1, 1000 );
And or they set
renderer.setSize( window.innerWidth, window.innerHeight );
But if instead you based the size of the canvas off its clientWidth and clientHeight then it would just always work. The user can make container and set it's CSS to any size. For example
<style>
html, body {
   width: 100%;
   height: 100%;
   margin: 0px;
   padding: 0px;
   overflow: hidden;  // make sure no scrollbar
}
#c {
   width: 100%;
   height: 100%;
}
</style>
<body>
  <canvas id="c"></canvas>
</body>
And it just works. If they change the canvas to be some specific size like
#c {
  width: 400px;
  height: 300px;
}
Then it still just works.
If they put the canvas inside some other containers (say they are making an editor and there's a side tab) it all just works. Whereas the way three is now you always have to go muck with the code for a different use cases.
You'd have to stop mucking with style.width and style.height inside three but as it is it seems like three is fighting the web platform instead of embracing it.
Thoughts?
To be clear see these samples. They all use exactly the same JavaScript (in fact they're all pointing to the same JavaScript file).  Because they use CSS and clientWidth, clientHeight nothing needs to change
Full size window with user created canvas
http://greggman.com/downloads/examples/three-by-css/three-by-css-01.html
80% size with user created canvas (size the window)
http://greggman.com/downloads/examples/three-by-css/three-by-css-02.html
Inline for article with user created canvas
http://greggman.com/downloads/examples/three-by-css/three-by-css-03.html
Full size window using container div where three.js makes the canvas
http://greggman.com/downloads/examples/three-by-css/three-by-css-by-container-01.html
80% size with using container div where three.js makes the canvas (size the window)
http://greggman.com/downloads/examples/three-by-css/three-by-css-by-container-02.html
Inline for article using container span where three.js makes the canvas
http://greggman.com/downloads/examples/three-by-css/three-by-css-by-container-03.html
No container div, uses the body, three.js creates the canvas
http://greggman.com/downloads/examples/three-by-css/three-by-css-body-only.html
This one is same as inline but uses box-sizing: box-border; which mean even though the container is set to 400px/300px the border is subtracted from the inside so the canvas needs to be 390/290 and again it just works because that's what clientWidth and clientHeight are.
http://greggman.com/downloads/examples/three-by-css/three-by-css-by-container-04.html
Hmmm, as far as I'm aware the renderer should already getting the size from the passed canvas? If not, that's a bug :)
As per the camera... how about making a method for that?
camera.setAspectFromRenderer( renderer );
                    // [requestAnimationFrame] should always go at the bottom. That way you can actually debug. // With it at the top the problem is even if your code causes an exeption // you've already asked the browser to queue a new event so the code will // keep executing
@mrdoob comment?
// [requestAnimationFrame] should always go at the bottom. That way you can actually debug. // With it at the top the problem is even if your code causes an exeption // you've already asked the browser to queue a new event so the code will // keep executing@mrdoob comment?
Heh! Interesting. If I remember correctly, the reason I put it on the top is because @greggman himself once suggested somewhere to have it on top to avoid ms delays consumed by executing the code. Happy to change the pattern.
As for where to put RaF, I probably did say that :-P
As for the rest though, nearly all the three examples use window.innerWidth/window.innerHeight which means when someone wants to use them as a base they have code to edit if their use case is different. If all the samples instead used canvas.clientWidth, canvas.clientHeight for both setting the the size of the canvas and for setting the aspect ratio they wouldn't have to edit any code to repurpose the samples.
Also, the default for three.js is that when you call setRendererSize it changes the css size. It seems like it should never set the css size, no? The 7 samples I posted are trying to demonstrate that given a certain default the same code works for 7 use cases with no changes whereas nearly all of the examples require changes for any use case other than "fill the window"
For the specific case of setting the aspect, yea, a method sounds good. I'd consider changing all the examples to use it though (yea, I know, work :P) Just passing on the idea.
Also, the default for three.js is that when you call setRendererSize it changes the css size. It seems like it should never set the css size, no?
That's because three.js takes care of devicePixelRatio for you. In your use cases the user would need to take care of it, no?
no, devicePixelRatio works just fine in all those samples.
On Tue, Jun 10, 2014 at 6:31 AM, Mr.doob [email protected] wrote:
Also, the default for three.js is that when you call setRendererSize it
changes the css size. It seems like it should never set the css size, no?That's because three.js takes care of devicePixelRatio for you. In your
use cases the user would need to take care of it, no?—
Reply to this email directly or view it on GitHub
https://github.com/mrdoob/three.js/issues/4903#issuecomment-45546888.
In fact even better, with clientWidth and clientHeight used every where
appropriate, if you explicitly set the size of the canvas everything else
still just works.
In other words if I remove the code that adjusts the size of the canvas the
rest of the code that uses clientWidth and clientHeight still works.
Here's the same 3 examples with the canvas set to an explicitly small size
so to make it clear I'm explicitly setting the canvas size and yet the
aspect ratio is still correct. Even as I scale the window the aspect ratio
stays correct without changing the size of the canvas.
http://greggman.com/downloads/examples/three-by-css/three-by-css-explicit-canvas-size-01.html
http://greggman.com/downloads/examples/three-by-css/three-by-css-explicit-canvas-size-02.html
http://greggman.com/downloads/examples/three-by-css/three-by-css-explicit-canvas-size-03.html
Could also make 3 samples that use THREE to set the canvas size but still
let CSS pick the display size (like all these samples do)
On Tue, Jun 10, 2014 at 7:15 AM, Gregg Tavares [email protected] wrote:
no, devicePixelRatio works just fine in all those samples.
On Tue, Jun 10, 2014 at 6:31 AM, Mr.doob [email protected] wrote:
Also, the default for three.js is that when you call setRendererSize it
changes the css size. It seems like it should never set the css size, no?That's because three.js takes care of devicePixelRatio for you. In your
use cases the user would need to take care of it, no?—
Reply to this email directly or view it on GitHub
https://github.com/mrdoob/three.js/issues/4903#issuecomment-45546888.
Here's ones that explicitly set the size by calling renderer.setSize but
still use clientWidth clientHeight where appropriate
On Tue, Jun 10, 2014 at 6:31 AM, Mr.doob [email protected] wrote:
Also, the default for three.js is that when you call setRendererSize it
changes the css size. It seems like it should never set the css size, no?That's because three.js takes care of devicePixelRatio for you. In your
use cases the user would need to take care of it, no?—
Reply to this email directly or view it on GitHub
https://github.com/mrdoob/three.js/issues/4903#issuecomment-45546888.
Also see @greggman's article: http://games.greggman.com/game/webgl-anti-patterns/
So, you are proposing:
clientWidth and clientHeight.clientWidth or clientHeight changes.window.devicePixelRatio to Mesh*Material.wireframeLinewidth, Line*Material.linewidth and PointsMaterial.size.In order to transition to that, how about we add a property to the renderer by now?
var canvas = document.getElementById( 'viewport' );
var renderer = new THREE.WebGLRenderer( { canvas: canvas } );
renderer.autoResize = true;
Or maybe we can assume we're in auto resize mode when the user passes a canvas in the constructor?
What happens when we serialise the data (like when we save in the editor)? Say that we serialise PointsMaterial.size and the user's computer window.devicePixelRatio is 2. Should we do PointMaterial.size / window.devicePixelRatio at serialising time?
That's a good point. One problem there are several use cases.
Case #1: The user wants PointsMaterial.size to be the smallest point possible. 
In other words they want gl_PointSize = 1.0 and they want the smallest point the GPU can render so they don't want to multiply device pixel ratio. This might be less true for points? It's certainly common for lines.
Case #2: The user wants PointsMaterial.size to be device independent but not view dependent.
In this case it's generally gl_PointSize = desiredSize * devicePixelRatio;.
Case #3: The user wants PointsMaterial.size to be device independent _and_ view dependent. 
In other words they want the points to get smaller based on their distance from the camera. This one probably also includes devicePixelRatio. Unfortunately it's actually kind of a bogus case because see case #4 
Case #4: The user wants PointsMaterial.size to be device independent _and_ view dependent _and_ match the unit size.
This is something like gl_PointSize = desiredSize * resolutionFactor * view;
Note there is no devicePixelRatio here. If the display is 4000 pixels across and a 1 unit cube 12 units in front of the camera ends up being rendered 50x50 pixels then gl_PointSize would end up being 50.
Maybe this is how you really want PointMaterial to work most of the time? That way regardless of the size of the user's display points will be the same size relative to other things in the scene.
If cube is 1x1x1 unit and the desired point size = 1 then a point displayed at the same world location is the same size as the cube.
We currently do that for Sprite and PlaneGeometry (#3894).
There's also arguably Case #5: The user wants unit sized points but not view dependent. In other words
 gl_PointSize = desiredSize * halfResolutionOfRenderTarget;
So a 1 unit point takes the same space as a 1 unit quad. Basically the same as #4 but without a view.
In any case it seems like PointsMaterial should support #4 if it doesn't already. That would make the users's scene show up the same on all devices. If you put some points around a cube they'll stay the same relative size across devices and there's no reference to devicePixelRatio.
The approach by @greggman appears to be working nicely, but I am having trouble getting points to be sized the same on devices with different DPRs -- both when attenuated, and when not.
Lines and wireframes look good.
For reference, here is my testbed: http://jsfiddle.net/LbLfu76e/
As for setting the aspect when creating the camera you could have it check if there's only 3 arguments. Or for that matter no arguments, 1, 3, 4,
 0 args = 45, auto, 0.1, 1000
 1 args = arg0, auto, 0.1, 1000
 3 args = arg0, auto, arg1, arg2
 4 args = arg0, arg1, arg2, arg3
auto meaning auto could either be 1 or it could set the camera's auto set aspect ratio flag in which case calling render.scene(...camera) would set the aspect to whatever the current render target is? With ES6 you can name the args. 
You could also make the camera constructor take an object
 new THREE.PerspectiveCamera({ fov: 45, zNear: 1, zFar: 1000});
And check in the constructor if the first argument is an object.
I guess auto adjust on the camera also has issues. Ideally you want to set the aspect to canvas.clientWidth / canvas.clientHeight but if you're rendering to render target you don't know the aspect ratio. You can assume it's width / height but you don't know for sure. I suppose in the rare case where it's not the user can not set auto on the camera.
if you're rendering to render target you don't know the aspect ratio. You can assume it's width / height but you don't know for sure.
@greggman What, in your view, is the use case in which width / height would not be correct?
Actually, we have been doing it wrong. The camera's aspect should be the same as the viewport's aspect -- not the canvas asspect -- unless, of course, the viewport is the same size as the canvas.
The camera aspect and the viewport are unrelated.
  <canvas width="100" height="200" style="width: 400px; height: 300px"></canvas>
This will give you a canvas that is 100x200 pixels (thin and tall) but displayed 400x300 pixels (wide and short).
To render to the entire thing (not have your vertices clipped) you need to set the viewport to 100x200 which is gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); or more correctly gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
To render to the correct aspect ratio it's aspect = 400/300 or aspect = canvas.clientWidth / canvas.clientHeight.
You can see examples here.
@greggman You are constructing an unusual example. I am not aware of any three.js example where the user wants the canvas stretched. (Rescaled for performance reasons -- maybe -- but only if the aspect is unchanged.)
Consider a three.js example in which the viewport is a (proper) subset of the canvas. For simplicity, assume DPR is 1.
renderer.setSize( 800, 1200 ); // tall and thin
renderer.setViewport( 200, 500, 600, 400 ); // short and wide
var viewportAspect = 600 / 400;
camera = new THREE.PerspectiveCamera( 40, viewportAspect, 1, 1000 ); // correct
To prevent distortion of the rendering, the camera aspect must match the viewport aspect. This is always the case.
So, continuing with your suggestion of using CSS-based sizing, the resize() callback would look like this:
function onWindowResize() {
    var canvas = document.getElementById( 'canvas' );
    // get the display size in CSS pixels -- also see canvas.getClientBoundingRect()
    var width = canvas.clientWidth;
    var height = canvas.clientHeight;
    // reset the size of the drawing buffer
    renderer.setSize( width * dpr, height * dpr, false ); // pass false here! // <=== scale by dpr
    // reset the viewport (because .setSize() changes it)
    var viewportWidth = 600 * dpr;
    var viewportHeight = 400 * dpr
    renderer.setViewport( 20 * dpr, 20 * dpr, viewportWidth, viewportHeight );
    // reset the camera
    var aspect = viewportWidth / viewportHeight;
    // orthographic
    camera.left = 0.5 * frustumSize * aspect;
    camera.right = - 0.5 * frustumSize * aspect;
    camera.top = 0.5 * frustumSize;
    camera.bottom = - 0.5 * frustumSize;
    // perpsective
    camera.aspect = aspect;
    camera.updateProjectionMatrix();
    // render
    render(); // if no animation loop
}
                    These are not three.js but I disagree that having the canvas's display size not match it's backbuffer size is not normal. Most of the samples by me on this page have a fixed size canvas regardless of the size it's displayed.
Maybe I'm missing it but it feels objectively true that most people want the aspect ratio to end up drawing things where 1 unit in X = 1 unit in Y. That that happens to be true if the canvas's display size is the same as it's backbuffer size is irrelevant. You can do the correct math and it will always be correct regardless of what the whether the CSS is makes them match or not. Why fight that?
What do gain from not following that?
In fact, given the fact that asking for a certain size is not guaranteed in WebGL if you don't do that you'll get the wrong result. Three.js saw this on the 5k imac. You'd ask for some giant canvas, you'd get back some smaller canvas than you asked for that was not the aspect ratio you asked for.  Setting the viewport to drawingBufferWidth, drawingBufferHeight and setting the aspect to clientWidht / clientHeight will continue to give you an image that appears correct. Not doing it will give you a distorted image.
With all due respect, it appears to me you misunderstood my previous post. I will restate it.
clientWidth / clientHeight.Sorry West, you're right. I probably mis-read your example. In your example I assume you want to set the viewport to 600x400 at 20x20?
This is a complicated area. Let's say your on an iMac5K.
You do this
var width = canvas.clientWidth;       // 2560
var height = canvas.clientHeight;     // 1440
// reset the size of the drawing buffer
renderer.setSize( width * dpr, height * dpr, false ); // pass false here! // <=== scale by dpr
you passed 5120, 2880 but you actually get 4096x2880  In other words
console.log(canvas.drawingBufferWidth, canvas.drawingBufferHeight);
prints
4096 2880
So now the question is how to do set viewport and the aspect. Let's assume the user wants to split the canvas 2x1, so 50% on each size. Currently they'd likely pass in a viewport width of 1280. If you really want to make it 50% then it's
  viewportX = desiredViewportX * gl.drawingBufferWidth / canvas.width;
  viewportY = desiredViewportY * gl.drawingBufferHeight / canvas.height;
  viewportWidth = desiredViewportWidth * gl.drawingBufferWidth / canvas.width;
  viewportHeight = desiredViewprotHeight * gl.drawingBufferHeigth / canvas.height;
  gl.viewport(viewportX, viewportY, viewportWidth, viewportHeight);
The aspect is still a separate issue because it's still being displayed the size requested. The correct aspect in this case seems like it would be
 var visualWidth = viewportWidth * canvas.clientWidth / gl.drawingBufferWidth;
 var visualHeight = viewportHeight * canvas.clientHeight / gl.drawingBufferHeight;
  camera.aspect = visualWidth / visualHeight;
That example fits this one where arguably the desire is to split the screen at 50% across and then split the right half at 50% down. So, if you have a canvas that is being displayed where the canvas's drawingBuffer size doesn't match its display size then you'd need to do the math as above.
I thought I'd revisit this again. I hope this is a simpler and more concrete example. I also hope I'm not forgetting anything above.
I'm not sure this is still a goal but at one point I thought someone said a goal of three.js was to be as simple as possible.
These changes would remove a few lines of JavaScript from nearly every sample but add a few lines of CSS. It would also make the remain code more flexible/portable.
So, in particular
To stay backward compatible I added
renderer.setAutoSize( true )
With that set then when you call
renderer.render( scene, camera )
The camera's aspect is set to
camera.aspect = _canvas.clientWidth / _canvas.clientHeight;
Could also make it handle render targets as well I think
That would remove at least one step. The line camera.aspect = .... in the resize function in nearly every sample would disappear
You could also change THREE.PerspectiveCameras signature so it's both
THREE.PerspectiveCamera(fov, aspect, zNear, zFar)
and
THREE.PerspectiveCamera(fov, zNear, zFar)
Which I did in the sample because once the camera is automatic there's no reason to even care what the aspect is for most projects so no need to pass it in anywhere.
Is that not a win?
I could do the same for and Orthographic camera and make it default to the width and height of the canvas if autoSize is true.
Again, to be backward compatible only if you call renderer.setAutoSize( true );
That would remove several more lines of code from most projects. No need to call renderer.setSize period, no need to set up a window resize handler, no need for the window resize handler code. (again see example)
Thoughts?
I like it! But I think the multiple constructor parameters is an issue.
I would even go with THREE.PerspectiveCamera2, THREE.AutoPerspectiveCamera, ...
If those are the only choices I'd pick THREE.PerspectiveCamera2.
I'm hoping that if you approve this I can refactor all the examples to be this style which would basically make THREE.PerspectiveCamera deprecated though.
I'm curious why it's bothersome. It's a common JavaScript pattern. The most common pattern is the last argument is a callback and all previous arguments are optional. gl.texImage2D is another with a 6 argument and a 9 argument version. 
Another idea would be to make the first parameter to THREE.PerspectiveCamera a number or object. So you could do this
var camera = new THREE.PerspectiveCamera({ fov: 60, near: 0.1, far: 300 });
Thoughts?
@greggman CSS-based sizing is default in infamous. You can see it, for example, by resizing the browser window on this pen, the buttons and layout remain the same size (the default perspective camera is adjusted by infamous so that a size of 1 in Three.js corresponds to 1 CSS px). The buttons themselves are <button> DOM elements, while the shadows and lighting are WebGL (Three.js).
The way that I'm doing this (implementation) doesn't work well for complicated scenes with deep trees, because the deeper the tree, the more floating point error it introduces which will start to mis-align WebGL and DOM content. In that example it's fairly simple; minimal depth to the scene graph.
I'm thinking of how to improve this by extending Three.js core to take over the matrix math (TODO).
(WebGL in Infamous started with your webglfundamentals.org, btw. Thanks! 😃 )
It's been 5 years since this topic came up. Now that you've had experience with VR support were the system chooses the aspect and size for you as well as your ModelViewer where you let CSS dictate the size of viewer maybe you have some new thoughts on this?
Most helpful comment
If those are the only choices I'd pick
THREE.PerspectiveCamera2.I'm hoping that if you approve this I can refactor all the examples to be this style which would basically make
THREE.PerspectiveCameradeprecated though.I'm curious why it's bothersome. It's a common JavaScript pattern. The most common pattern is the last argument is a callback and all previous arguments are optional.
gl.texImage2Dis another with a 6 argument and a 9 argument version.Another idea would be to make the first parameter to
THREE.PerspectiveCameraa number or object. So you could do thisThoughts?