Enhancing depth perception in three.js without distorting the foreground

I am facing a challenge with my three.js scene where I have a floor plane with a specific texture that needs to align perfectly with a 2D overlay.

The issue arises when I try to adjust the camera FOV to achieve the desired perspective. Increasing the FOV results in stretching of the foreground, which is not ideal. To make the texture follow the wall correctly, I need to set a very high FOV (~120-150), but this causes the camera to render objects located behind it. Additionally, moving the camera closer to the center of the scene just to show the entire floor feels incorrect. How can I fine-tune this scene to obtain the right perspective without any distortion?

For a live example, please visit:

The key camera settings are as follows:

var WIDTH = 1024;
var HEIGHT = 683;
var FOV = 140; // Higher values increase perspective but also introduce distortion

camera = new THREE.PerspectiveCamera(FOV, WIDTH/HEIGHT, .1, 3000);

camera.position.x = 0;
camera.position.y = 200;
camera.position.z = 0;  
//camera.lookAt(scene.position);
camera.lookAt(new THREE.Vector3(0, 100, -400)); // look at the back of the room
camera.updateProjectionMatrix();

Answer №1

When adding a virtual object to an existing image, it is important to match the original camera parameters as closely as possible. If the image is synthetic, obtaining the parameters from the creator would be ideal. Otherwise, some parameters can be inferred by analyzing the image.

For this analysis, let's assume a pinhole camera model with the optical axis at the center of the image. Parallel lines in the scene will converge at a vanishing point. In this specific image, we can accurately determine only one vanishing point:

The parallel lines in the image are also parallel to the ground plane, indicating that the vanishing point lies on the horizon. The horizon appears slightly below the center of the image, suggesting that the camera is pitched upwards.

Calculating the exact pitch, we find that the vanishing point is approximately 0.022 image heights (15 pixels) below the center of the image. This allows us to express the camera pitch in terms of the vertical field of view:

pitch = arctan(2 * tan(vfov/2) * 0.022)

We can also determine the yaw of the camera relative to the parallel lines. The vanishing point is about 0.010 image heights right of center, indicating a slight leftward yaw of the camera.

yaw = arctan(2 * tan(vfov/2) * -0.010)

In our three.js application, I adjusted the camera configuration section of the code accordingly:

var FOV = 75;

[...]

camera.position.x = 0;
camera.position.y = 200;
camera.position.z = 512;

var lookTarget = new THREE.Vector3().copy(camera.position);
var tanScale = 2 * Math.tan(FOV/2 * Math.PI/180);
lookTarget.x += -0.010 * tanScale;
lookTarget.y +=  0.022 * tanScale;
lookTarget.z += -1;
camera.lookAt(lookTarget);

camera.updateProjectionMatrix();

To minimize perspective distortion, I moved the camera back and adjusted the field of view to 75 degrees. This resulted in the texture converging at the vanishing point more naturally:

Increasing the size of the floor geometry may be necessary to fully display the end of the floor due to the assumed depth resulting from a smaller field of view. Adjusting the field of view can fine-tune the texture foreshortening without altering the position of the vanishing point. Estimating the focal length of the image may be challenging, but in images with more features, it could be achievable, allowing inference of the field of view as well.

Answer №2

After some experimentation, the solution was found by adjusting the camera tilt upwards to extend the distance falloff instead of increasing the FOV

var FOV = 100;
camera.position.x = -100;
camera.position.y = 540;
camera.position.z = 3500;   // Moved further back
//camera.lookAt(scene.position);
camera.lookAt(new THREE.Vector3( 0, 590, -1000 )); // look at the back of the room AND TILT UPWARDS!

Answer №3

It's my opinion that using fixed numbers in your code can make it look bad and may not be able to handle all situations. Have you considered trying the generic lookAt method in Open GL? This method typically takes three arguments: eye, center, and an upside-down vector.

In my experience with Webgl games, I often place the camera behind an object. You can check out this helpful link for more information:

If that doesn't work for you, you could try implementing a more generic solution like the following:

     if(typeof fc == "undefined") fc=0;
      var delta =0.02;
      var y=object.rotation.y;
      var dir=object.rotation.y-fc;
      var fc = THREE.Math.clamp( fc + 2 * delta * dir, y-control.followRadius, y+control.followRadius );
      var sn = Math.sin( fc);
      var cs = Math.cos(fc);
     camera.rotation.set(0,0,0);
     camera.position.x=object.position.x+(control.positionOffsetZ*sn);
     camera.position.y=object.position.y+control.positionOffsetY;   
     camera.position.z=object.position.z+(control.positionOffsetZ*cs);

     camera.lookAt(new THREE.Vector3(
         object.position.x+(control.targetOffsetZ*sn)
         ,object.position.y+control.targetOffsetY
         ,object.position.z+(control.targetOffsetZ*cs)
        ));
    var control = {"positionOffsetZ":-39,"positionOffsetY":3.2,"targetOffsetZ":5,"targetOffsetY":0,"followRadius":0.35};

Just imagine that the "object" mentioned is a dummy invisible cube that your camera is attached to, which you move using key controls. Simply copy and paste this code snippet into your project, and things should work smoothly. Feel free to ask if you have any questions or doubts.

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

Ways to direct to dashboard if a user is logged in using AngularJS

As a newcomer to AngularJS, I am facing an issue with redirecting my page to the dashboard once the user has logged in. I receive an access token after login, which I save in cookies. Despite trying solutions from Stack Overflow, I have not been able to re ...

Tips for emphasizing the current element without disrupting existing styles

Is there a way to highlight an element with a border without causing it to shift? The script seems a bit glitchy when detecting mouse leaving the area. I'm unable to override parent element properties like border or outline. I also can't use pse ...

Guidelines for sorting through items in Vue 3

In the past, Vue 2 allowed us to easily filter items using the | and filters syntax. However, this method is no longer supported in Vue 3. Instead, we now use the "computed" property to transform a single value into another. But what about changing values ...

Enabling render.shadowmap in Three.js decreases the maximum distance that can be illuminated by a pointLight

I'm currently developing a solar system simulator with three.js, and I am striving to incorporate shadows in order to replicate situations where moons obstruct sunlight from reaching their respective planets and vice versa. After extensive research on ...

Transferring data between two screens within an Ionic app by harnessing the power of angularJS

As a beginner in Ionic and Angular development, I am currently facing an issue with my Ionic application. I have two pages - one with drop-down selects and a submit button, and another page to process the user's choices and display the results. The pr ...

Quantifying the passage of time for data entry

Looking to create a quiz form where I can track how long it takes users to input/select answers. I attempted the following code but the timing seems off: $('input, select').on('focus', function(event) { el = $(this); name ...

Issue with displaying content within a custom element for children was not seen

The content within the 'Child content' span is appearing in the Light DOM, but for some reason it's not being displayed on the actual page (refer to the screenshot provided). Does anyone have any insights as to why it might not be visible? ...

Why am I encountering http://localhost:3000/api/auth/error?error=AccessDenied when implementing Google signin in my Next.js application?

Can someone please assist me in resolving an issue I am facing with my NextJs application using Google signin? Whenever I try to sign in, I get the error message "http://localhost:3000/api/auth/error?error=AccessDenied". Why is this happening? Here is a ...

The functionality of jQuery is not functioning properly on dynamically created identical divs

I am currently working on creating a comment reply section. I have managed to load the same div for reply that I use for commenting by utilizing $('#1').append($('.enterComment').html());. In this case, 1 represents the id of the div th ...

Ways to dynamically incorporate media queries into an HTML element

Looking to make some elements on a website more flexible with media rules. I want a toolbar to open when clicking an element, allowing me to change CSS styles for specific media rules. Initially thought about using inline style, but it turns out media rul ...

Adjusting the height of an Ionic list item when the text overflows is not functioning properly

Snippet of Code: <ion-header-bar align-title="center" class="bar-positive"> <div class="button button-clear" ng-click="closeGlobalTaskModal()">Cancel</div> <h1 class="title">Add Global Task</h1> <div class="bu ...

The callback function of jQuery getJSON would sometimes not be triggered

I've been struggling with this issue for hours now, and I can't seem to figure out why. Oddly enough, q seems to be functioning correctly. The URL returns a valid JSON response, displaying objects and arrays in the JSON tab under the Net-tab ...

Looking for help combining HTML5 Canvas and JavaScript to showcase images. Can anyone lend a hand?

I am currently in the process of developing an Android game using HTML5 and Javascript. I have managed to make some progress, but I have encountered a problem that has left me puzzled. Initially, my JS file and Html file were functioning well together for ...

The fixed position element appears to be failing to stay fixed in Google Chrome

Something strange is happening with a fixed position element in Chrome. It is still scrolling with the page even though it should be fixed. To better understand the issue, you can view it on the live site In Firefox and even IE, the "Block 1 Block 2 Block ...

Connecting promises to build upon past outcomes

Can someone help me figure out how to access previous results in promises? I've been following the guidance on this stackoverflow thread. I had success with 2 promises, but things got tricky when I added a 3rd promise that makes a POST request to an ...

Is converting the inputs into a list not effectively capturing the checkbox values?

On my website, I have a div that contains multiple questions, each with two input fields. When a button is clicked, it triggers a JavaScript function to add the input values to a list. This list is then intended to be passed to a Django view for further pr ...

Setting up Vue CLI 4 with ESLint, TypeScript, Stylelint for SCSS, and Airbnb rules in the VS Code editor with automatic fixes on save

After struggling with configuring Vue CLI 4 with ESLint, Prettier, Airbnb rules, TypeScript, and Vetur, I found myself at a crossroads. The challenges continued to mount as the nature of the problem evolved from my previous attempts.: How to configure Vue ...

How can I remove all javascript code from a string using PHP?

Recently, I came across some PHP code that raised concerns: $mystr = "<script>window.onload = function(){console.log('Hi')}</script>"; $mystr .= "<div onmouseover='alert('Hi')'></div"; The goal here is t ...

Is there a way to refresh autocomplete/autofill after form submission with a custom JavaScript function?

I've developed a Vue application that includes a form. Once the user clicks on submit, an Ajax request is made using Axios through a JavaScript function. The data being sent is a custom JSON object that I have constructed by combining the information ...

How can I protect a text or script file from being accessed or downloaded by entering its URL directly into the browser address bar?

While working on my JSP file, I have incorporated some Java-script code but to safeguard it from being visible to clients, I decided to store it in a separate codescript.js file and load it using ajax like so: $.ajax({ cache: true, dataType: "script ...