ThreeJS: Calculating FOV for perspective camera after browser window resize
Asked Answered
A

1

6

I need to obtain the correct Three.JS camera FOV after the browser window size has changed. I have viewed the following questions, but can't seem to find an answer for my question:

My camera is set up like this ('this' refers to a gameCamera object I setup):

const CAMERA_DIST = 8000;   

-other stuff- 

this.camera = new THREE.PerspectiveCamera(
  45,                                      //FOV parameter
  window.innerWidth / window.innerHeight,  //aspect ratio parameter
  1,                                       //frustum near plane parameter
  CAMERA_DIST                              //frustum far plane parameter
);

When the browser window is resized by the user, the following update code is called. I included code I found here: (How to calculate fov for the Perspective camera in three js?) for trying to calculate a new FOV ('aFOV').

function onWindowResize() {
  gameCamera.camera.aspect = window.innerWidth / window.innerHeight;
  console.log(gameCamera.camera.fov);
  gameCamera.camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
  console.log(gameCamera.camera.fov);
  let aFOV = 2*Math.atan((window.innerHeight)/(2*CAMERA_DIST)) * (180/Pi);
  console.log(aFOV);
  windowHalfX = window.innerWidth / 2;
  windowHalfY = window.innerHeight / 2;
} //onWindowResize()

But it doesn't seem to work. After the window is resized, for example dragging it 500 pixels wider, I can see a much greater width of the rendered 3D scene. The rendered view doesn't seem to distort (i.e. no more or less 'fisheye' appearance). But the camera.fov value doesn't change (in the console log, I get '45' as FOV before, and '45' as FOV afterwards) and my calculated FOV is not at all correct -- with a value of '6.33189...'.

Thus it seems to me that FOV is used to set up the projectionMatrix, but when updateProjectionMatrix() is called, the reverse calculation is not done to update FOV. I am using THREE.JS r87 (revision 87).

Here is the original code I found at this (How to calculate fov for the Perspective camera in three js?) link for calculating FOV:

var height = 500;
var distance = 1000;
var fov = 2 * Math.atan((height) / (2 * distance)) * (180 / Math.PI);
itsLeftCamera = new THREE.PerspectiveCamera(fov , 400 / 500, 1.0, 1000);

The comments at that link seem to indicate that the formula is correct.

Questions:

  • Am I correct that camera.updateProspectiveMatrix() doesn't change the .fov property?
  • Is my understanding that the FOV changes when the screen gets wider correct? or am I somehow confused about what FOV means?
  • What am I doing wrong? How do I correctly calculate the camera FOV?

Thanks in advance

--- edit ----

After asking the question, I decided to try to see if I could figure this out.

I started with the excellent diagram from @rabbid76 found here: Calculating frustum FOV for a PerspectiveCamera I modified the image to show derivation of formula for calculating FOV if one knows the far plane dimensions and the camera distance.

enter image description here

I understand now the derivation of the formula. And I see that if given a starting vertical FOV, then I can calculate my far plane width and height as follows:

  this.cameraDist = CAMERA_DIST;
  this.aspectRatio = window.innerWidth / window.innerHeight;
  this.farPlaneHeight = Math.tan(this.vertFOV/2) * this.cameraDist;
  this.farPlaneWidth = this.Height * this.aspectRatio;

But I'm still stuck. I don't understand the correlation between the rendering window size (i.e. the browser window) and the far plane size. If my cameraDist is huge (e.g. 1,000,000), my far plane will also be huge.

I assume my rendering window is somewhere between the near plane and the far plane. So what I need is the distance to the rendering plane.

I still can't figure out how to determine the new camera FOV after changes to window.innerHeight or window.innerWidth.

-- edit 2 --

@rabbid76 suggested the correct answer in a comment. I wanted to understand it, so made some diagrams while I considered this:

enter image description here

What the diagram shows is that as the viewport plane changes sizes (h1 --> h2), one can calculate the resulting change in effective field of view (FOV).

If the viewport is not square, then one can also use this formula to calculate the horizontal FOV if the vertical FOV is known and the aspect ratio is known.

To put this all for a final answer, I use the following code:

//Below is code used when initializing the perspective camera
this.viewportWidth = window.innerWidth;
this.viewportHeight = window.innerHeight;
this.aspectRatio = window.innerWidth / window.innerHeight;
this.vertFOV = params.FOV || CAMERA_FOV
this.horizFOV = this.calculateHorizFOV();
this.camera = new THREE.PerspectiveCamera(this.vertFOV, this.aspectRatio, 1, this.cameraDist);
...
calculateHorizFOV() {
  let radVertFOV = this.vertFOV * Pi/180;
  let radHhorizFOV = 2 * Math.atan( Math.tan(radVertFOV/2) * this.aspectRatio);
  let horizFOV = radHorizFOV * 180/Pi;
  return horizFOV;
} 

Then, when the user resizes the screen, I use this code.

function onWindowResize() {
  let oldHeight = gameCamera.viewportHeight;
  let oldWidth = gameCamera.viewportWidth;
  let newHeight = window.innerHeight;
  let newWidth = window.innerWidth;
  gameCamera.viewportHeight = newHeight;
  gameCamera.viewportWidth = newWidth;
  gameCamera.aspectRatio = newWidth / newHeight;
  let oldRadFOV = gameCamera.vertFOV * Pi/180;
  let newRadVertFOV = 2*Math.atan( Math.tan(oldRadFOV/2) * newHeight/oldHeight);
  gameCamera.vertFOV = newRadVertFOV * 180/Pi;
  gameCamera.calculateHorizFOV();  
  gameCamera.camera.aspect = gameCamera.aspectRatio;
  gameCamera.camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
} //onWindowResize()
Alexalexa answered 8/11, 2017 at 16:1 Comment(6)
camera.fov is the vertical FOV -- not the horizontal FOV. See #17838152.Bekki
It occurs to me also, that there should be a horizontal FOV and a vertical FOV. In my discussion above, I just changed the width of the screen, so that would change the horizontal FOV. But the formula I copied only considered the height. ... I see @Bekki also pointed this out. But it still doesn't answer my problem.Alexalexa
@Rabbid76, thanks for looking at this. My motivation is to be able to tell if a rendered object is visible in the viewport. If not, then I will back the camera away from the scene. I will do this by calculating angle between vector to object and camera's line of sight. If angle to object is more than 1/2 FOV, then It can't be seen. So all I want is FOV. I don't want to change perspective. I just need to respond when user resizes the browser window. This doesn't seem to change the perspective but does change the FOVAlexalexa
@Rabbid76 It will take me a bit to digest your suggested formula. ThanksAlexalexa
Maybe this will help: #14614752Bekki
thanks @WestLangley. Your comment that camera.fov was a vertical FOV was very helpful!Alexalexa
C
2

If you want to know, if a point is in the view volume and is not clipped, then you have to project the point on the viewport.
Use Vector3.project for this:

camera   = THREE.PerspectiveCamera
pt_world = Three.Vector3

pt_ndc = new THREE.Vector3()
pt_ndc.copy(pt_world).project(camera)

The result is a Cartesian coordinate and the point is in the view volume (on the viewport), if the result is in normalized device space. The normalized device space is in range from (-1, -1, -1) to (1, 1, 1) and form a perfect cube volume. (See Transpose z-position from perspective to orthographic camera in three.js)

Note, the projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport. The projection matrix transforms from view space to the clip space, and the coordinates in the clip space are transformed to the normalized device coordinates (NDC) in the range (-1, -1, -1) to (1, 1, 1) by dividing with the w component of the clip coordinates.

Crosspollinate answered 8/11, 2017 at 19:50 Comment(2)
I'm marking this as accepted answer because @Crosspollinate gave me the correct answer in a comment above, and I want him to get credit. This posting doesn't have the direct answer to my question, so see edit #2 in original question for explanation.Alexalexa
This answer does what I was ultimately trying to do (see if an object was in the viewport).Alexalexa

© 2022 - 2024 — McMap. All rights reserved.