4

How can one calculate the camera distance from an object in 3D space (an image in this case) such that the image is at its original pixel width.

Am I right in assuming that this is possible given the aspect ratio of the camera, fov, and the original width/height of the image in pixels?

(In case it is relevant, I am using THREE.js in this particular instance).

Thanks to anyone who can help or lead me in the right direction!

scottc
  • 101
  • 2
  • 10

3 Answers3

5

Thanks everyone for all the input!

After doing some digging and then working out how this all fits into the exact problem I was trying to solve with THREE.js, this was the answer I came up with in JavaScript as the target Z distance for displaying things at their original scale:

var vFOV = this.camera.fov * (Math.PI / 180), // convert VERTICAL fov to radians

var targetZ = window.innerHeight / (2 * Math.tan(vFOV / 2) );

I was trying to figure out which one to mark as the answer but I kind of combined all of them into this solution.

scottc
  • 101
  • 2
  • 10
  • Coming for the plain 2D world, I wonder if there is a "default" to initialize the scene with a z value that would render "real pixels". As far as I understood you snippet, this is what it does but I felt like it could/should be default – Ben Dec 12 '14 at 09:16
  • @Ben do we have to put this calculation while we initialise camera or it goes with render() function ? – rut2 Oct 18 '16 at 13:13
2

Trigonometrically:

A line segment of length l at a right angle to the view plane and at a distance of n perpendicular to it will subtend arctan(l/n) degrees on the camera. You can arrive at that result by simple trigonometry.

Hence if your field of view in direction of the line is q, amounting to p pixels, you'll end up occupying p*arctan(l/n)/q pixels.

So, using y as the output number of pixels:

y = p*arctan(l/n)/q
y*q/p = arctan(l/n)
l/tan(y*q/p) = n

Linear algebra:

In a camera with a field-of-view of 90 degrees and a viewport of 2w pixels wide, the projection into screen space is equivalent to:

x' = w - w*x/z

When perpendicular, the length of a line on screen is the difference between two such xs so by normal associativity and commutivity rules:

l' = w - w*l/z

Hence:

w - l' = w*l/z
z = (w - l') / (w*l)

If your field of view is actually q degrees rather than 90 then you can use the cotangent to scale appropriately.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • Thank you! I will try this out. My fov is actually 45 in this case. So I would scale z in your equation by the cotangent of 45? – scottc Jan 26 '13 at 03:06
  • In the first trigonometric version if I have a camera with an fov of 45, an screen resolution of 1024x768, and an image sized 120x115 pixels, I would end up with q=45, p=1024*768, l=115, and y=120*115 ? Or am I totally off-base? – scottc Jan 26 '13 at 03:19
  • No — it's all one dimensional; think in terms of the length of the base of a right angled triangle if you're looking down the other side that isn't the hypotenuse. – Tommy Jan 26 '13 at 04:14
  • So I reckon that if your geometry were 115 units across then it would need to be about 1300 units from the camera to be 115 px on screen with your 1024 px = 45 degrees viewport. – Tommy Jan 26 '13 at 04:19
  • So here is my implementation of equation. It would seem it works perfectly for a 1024px screen width, but beyond that it becomes increasingly smaller than the original width. Does this look right? `var targetZ = originalImageWidth / ( Math.tan( degToRadian(originalImageWidth * (45 / window.innerWidth ))) );` – scottc Jan 27 '13 at 23:06
  • Update: I implemented this equation like so (correcting what I wrote above) and verified that is gets the same values you are mentioning. `var z = originalImageWidth / ( Math.tan( this.degToRadian((targetImageWidth * fov) / (window.innerWidth) ) ) ); ` Unfortunately it still does not work well in Three.js with the CSS3D Renderer. It always comes out too small or too large depending on the screen size so I'm assuming something with the rest of my code is off. – scottc Mar 15 '13 at 21:00
  • Gotta get better in maths… any updates ? lots of answer when talking about the height, but it sure needs to be handled differently when the screen is in portrait mode – Ben Dec 28 '14 at 15:45
0

In your original question you said that you're using css3D. I suggest that you do the following:

Set up an orthographic camera with fov = 1..179 degrees, where left = screenWidth / 2, right = screenWidth / - 2, top = screenHeight / 2, bottom = screenHeight / - 2. Near and far planes do not affect CSS3D rendering as far as I can tell from experience.

camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
camera.fov = 75;

now you need to calculate the distance between the camera and object in such way that when the object is projected using the camera with settings above, the object has 1:1 coordinate correspondence on screen. This can be done in following way:

var camscale = Math.tan(( camera.fov / 2 ) / 180 * Math.PI);
var camfix = screenHeight / 2 / camscale;
  1. place your div to position: x, y, z
  2. set the camera's position to 0, 0, z + camfix

This should give you 1:1 coordinate correspondence with rendered result and your pixel values in css / div styles. Remember that the origin is in center and the object's position is the center of the object so you need to do adjustments in order to achieve coordinate specs from top-left corner for example

object.x = ( screenWidth - objectWidth ) / 2 + positionLeft
object.y = ( screenHeight - objectHeight ) / 2 + positionTop
object.z = 0

I hope this helps, I was struggling with same thing (exact control of the css3d scene) but managed to figure out that the Orthographic camera + viewport size adjusted distance from object did the trick. Don't alter the camera rotation or its x and y coordinates, just fiddle with the z and you're safe.

Community
  • 1
  • 1
Pekka Toiminen
  • 391
  • 3
  • 5