Determining Field of View (FOV) for a perspective camera in ThreeJS following a browser window resize

After changing the size of the browser window, I'm trying to determine the correct Three.JS camera FOV. I have looked at several related questions, but haven't found a solution yet:

  • How to calculate fov for the Perspective camera in three js?
  • Calculating frustum FOV for a PerspectiveCamera

This is how my camera is configured ('this' refers to a gameCamera object I created):

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 user resizes the window, the following update code is executed. I tried using code from this link (How to calculate fov for the Perspective camera in three js?) 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()

However, it doesn't seem to be working properly. When resizing the window by, for example, dragging it wider by 500 pixels, the rendered 3D scene appears much wider. The FOV value remains unchanged (both before and after resize) and the calculated FOV ('6.33189...') is incorrect.

It appears that the FOV is used to set up the projectionMatrix, but when updateProjectionMatrix() is called, the reverse calculation to update FOV doesn't happen. I am using THREE.JS r87 (revision 87).

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

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);

Now, I have the following questions:

  • Does camera.updateProspectiveMatrix() not change the .fov property?
  • Is my understanding correct that FOV changes as the screen gets wider? Or am I misunderstanding what FOV means?
  • What am I missing here? How can I accurately calculate the camera FOV?

Thank you in advance.

--- edit ----

After posting my question, I decided to try and figure it out myself.

I referred to a helpful diagram by @rabbid76 found here: Calculating frustum FOV for a PerspectiveCamera. I modified the image to illustrate the derivation of a formula for calculating FOV based on far plane dimensions and camera distance.

https://i.sstatic.net/ev2ay.png

I now understand the formula derivation and can calculate far plane width and height if given an initial vertical FOV:

  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;

However, I still need to determine the relationship between the rendering window size (browser window) and the far plane size. If my cameraDist is large (e.g., 1,000,000), the far plane will also be huge.

I assume the rendering window lies between the near and far planes. So, I need to find the distance to the rendering plane.

I'm still struggling with figuring out how to calculate the new camera FOV after changes to window.innerHeight or window.innerWidth.

-- edit 2 --

@rabbid76 provided the correct answer in a comment. I made some diagrams to help me understand it:

https://i.sstatic.net/cCikB.png

The diagram shows how changes in the viewport plane sizes (h1 --> h2) can affect the effective field of view (FOV). It also provides a formula to calculate horizontal FOV based on known vertical FOV and aspect ratio when the viewport is not square.

To summarize, 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()

Answer №1

To determine if a point is within the view volume and not clipped, it is necessary to project the point onto the viewport.
Utilize Vector3.project method like so:

camera   = THREE.PerspectiveCamera
pt_world = Three.Vector3

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

The outcome will be a Cartesian coordinate, confirming that the point exists within the view volume (on the viewport) if it falls within normalized device space. The normalized device space ranges from (-1, -1, -1) to (1, 1, 1) forming a perfect cube volume. (Refer to Transpose z-position from perspective to orthographic camera in three.js)

It's important to note that the projection matrix outlines the transformation from 3D scene points to 2D viewport points. This matrix converts from view space to clip space, then further transforms coordinates in clip space to normalized device coordinates (NDC) in the range of (-1, -1, -1) to (1, 1, 1) through division by the w component of clip coordinates.

https://i.sstatic.net/KxGkw.png

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Restrict the occurrence of a specific element in the array to a maximum of X times

Functionality: A feature in this program will randomly display elements from an array. Each element can only be shown a certain number of times. Issue: I am unsure how to limit the number of times each element in the array is displayed. Currently, all ...

Is there an issue with my JavaScript append method?

Within the following method, an object named o gets appended to a list of objects called qs. The section that is commented out seems to be causing issues, while the uncommented section is functional. What could possibly be wrong with the commented part? on ...

Tips for storing arrays in AngularJS with JavaScript

I am new to using JavaScript. I have a function that stores objects in an array to an Angular model. Here is an example: function getSpec(){ debugger var i; for(i=0;i<main.specifications.length;i++){ main.newProduct.Specification= ( ...

What was the reason for node js not functioning properly on identical paths?

When the search route is placed at the top, everything works fine. However, when it is placed at the end, the route that takes ID as a parameter keeps getting called repeatedly in Node. Why does this happen and how can it be resolved? router.get('/se ...

Retrieving currentUser data using Vue.js, Vuex, Express, and MongoDB

I am currently working on creating a MEVN stack authentication page that displays information about the user who is logged in. After successfully logging in, the router redirects to Home.vue and passes the username as a prop. The onSubmit method in Login. ...

How to retrieve the content/value from a textfield and checkbox using HTML

I'm encountering an issue with my HTML code where I am unable to extract data from the HTML file to TS. My goal is to store all the information and send it to my database. Here is a snippet of the HTML: <h3>Part 1 : General Information</h3 ...

Ways to switch Bootstrap 5 accordion element using JavaScript

I am using Bootstrap 5's accordion component and I would like to toggle this component from a custom JavaScript function called toggle, preferably without relying on jQuery. <!doctype html> <html lang="en"> <head> <meta ch ...

Is there a JavaScript method for opening a new window regardless of the PHP file being used?

I have encountered a small dilemma that is causing me frustration. Currently, I have set up a BUTTON on my website. This button, when clicked, triggers a JavaScript function to open a "New Window". The code for this button and function looks like this : ...

Vue.js transition-group does not apply the *-move class

As I dive into learning Vue, I find myself wondering if I may have overlooked something fundamental or stumbled upon a bug. Despite multiple readings of the documentation at https://v2.vuejs.org/v2/guide/transitions.html#List-Move-Transitions, I still can& ...

What is the best way to sort through an array depending on a specific sequence of elements provided

I am trying to create a custom pipe in Angular 5 that filters an array of events based on a given sequence. For instance, if my data is: ["submit", "click", "go_back", "click",...] I want to filter this data based on up to three inputs. If input ...

Matching wildcard paths using Express

Seeking help with routing in Express. I am trying to have both /m/objectID and /m/someslug/ObjectID directed to the same function. My current setup is as follows: app.get("/m/:id", ...); app.get("/m/(.*)/:id", ...); The first route is working properly, b ...

How to insert a span element within a link tag in Next.js/React.js

Looking to create a breadcrumb using microdata in a Next.js and React.js project. Initially, I tried the following: <li itemProp="itemListElement" itemScope itemType="https://schema.org/ListItem"> <Link href={'/index&a ...

Exploring the connection between two MongoDB collections

I currently have two collections in my MongoDB database: Category and Book Here is the category.js code: var mongoose = require("mongoose"); var Schema = mongoose.Schema; var categoryModel = new Schema({ catName: String, menuKey: String }); module.ex ...

The PHP counter conceals the comma upon loading and does not display it permanently

I'm currently working on a PHP counter and encountering an issue with the comma display. I have implemented Number Format in a PHP function to print counter digits with commas every 3 digits, but the comma doesn't remain visible after the page lo ...

Identifying added elements that respond to link hover effects

I've been working on a project where I'm trying to replace the default browser cursor with an SVG one using jQuery. Everything seems to be working smoothly, except for changing the cursor when it hovers over links - nothing I've tried so far ...

What is the best way to add a value to the value attribute of a div using JavaScript?

Is there a way to insert a value into the value attribute of a div using Internet Explorer 9? I have attempted various JavaScript functions, but so far I can only modify the content inside the div and not the value attribute itself. <div id="mydiv" val ...

Leveraging random attributes in Next.js without encountering the "server/client mismatch" issue

Is there a way to generate unique IDs for form inputs dynamically, in order to avoid conflicts with other elements that share the same ID? If multiple login forms are present on a single page, each with an 'email' field, setting the id property b ...

Retrieving data and selecting tables using jQuery

Currently, I have an HTML table displaying various available times for scheduling purposes. My goal is to allow users to click on a specific time slot and retrieve both the column header (staff person's name) and the value of that cell (the time). Aft ...

What is the method to assign a value to ng-class in Angularjs?

I need to categorize items in a list by assigning them classes such as items-count-1, items-count-2, items-count-3, items-count-4, based on the total number of items present. This is how I would like it to appear: li(ng-repeat="area in areas", ng-class=" ...

Issue with running the Jquery each function within a textbox inside an ASP.NET gridview

Below is the gridview markup: <asp:GridView ID="gvDoctorVisits" runat="server" DataKeyNames="AdmissionId" class="tableStyle" AutoGenerateColumns="False" Width="100%" EmptyDataText=& ...