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:
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.
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:
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()
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.