Changing z-coordinate from perspective to orthographic camera in three.js

In my project, I am trying to blend perspective objects (which appear smaller as they move farther away) with orthographic objects (which maintain the same size regardless of distance). The perspective objects are a part of the rendered "world," while the orthographic objects serve as adornments like labels or icons. Unlike traditional HUD elements, I want these orthographic objects to be rendered within the world, meaning they can be obscured by other world objects, such as a plane passing in front of a label.

My approach involves using one renderer but two scenes - one with a PerspectiveCamera and the other with an OrthographicCamera. By rendering them sequentially without clearing the z-buffer (setting the renderer's autoClear property to false), I aim to achieve this effect. However, the challenge lies in synchronizing the placement of objects in each scene so that an object in one scene is assigned a proper z-position relative to objects in the opposite scene, both in front and behind it.

To tackle this problem, I have designated the perspective scene as the "leading" scene, where all object coordinates (perspective and orthographic) are set based on this scene. Perspective objects use these coordinates directly and are rendered accordingly with the perspective camera. Orthographic objects' coordinates are transformed to match those in the orthographic scene, then rendered using the orthographic camera. This transformation involves projecting the coordinates from the perspective scene onto the perspective camera's view pane and back onto the orthogonal scene with the orthographic camera:

position.project(perspectiveCamera).unproject(orthographicCamera);

However, despite my best efforts, this method is not yielding the desired results. The orthographic objects consistently render in front of the perspective objects, even when they should be positioned between them. For instance, consider an instance where a blue circle should appear behind a red square but in front of a green square (which it currently does not):

// Inserted code snippets here

The challenge persists, even though conceptually positioning orthographic objects before each render could potentially solve the issue.

UPDATE:

To address this problem, I have proposed a current solution in the form of an answer below. Despite this mitigation, the performance may not fully align with expectations compared to the orthographic camera itself. Therefore, any insights into why the orthographic camera behaves unexpectedly or alternative solutions would be greatly appreciated.

Answer №1

You are very close to achieving the expected result. Don't forget to update the camera matrices for the project and unproject operations to work correctly:

pCam.updateMatrixWorld ( false );
oCam.updateMatrixWorld ( false );
circle.position.project(pCam).unproject(oCam);

Explanation:

In a rendering process, each mesh in the scene is typically transformed by the model matrix, view matrix, and projection matrix.

  • Projection matrix:
    Describes the mapping from 3D points in the scene to 2D points on the viewport. Transforms from view space to clip space, then to normalized device coordinates (NDC) ranging from (-1, -1, -1) to (1, 1, 1) by dividing with the w component of clip coordinates.

  • View matrix:
    Defines the direction and position from which the scene is viewed. Transforms the world space to the view (eye) space. In the viewport coordinate system, X-axis left, Y-axis up, Z-axis out of view (Note: in a right-hand system, Z-Axis is the cross product of X-Axis and Y-Axis).

  • Model matrix:
    Specifies the location, orientation, and relative size of a mesh in the scene. Transform vertex positions from mesh to world space.

The depth value determines if a fragment is drawn "behind" or "before" another fragment. For orthographic projection, Z-coordinate of view space is linearly mapped to depth value, but in perspective projection it's non-linear.

Depth value calculation:

float ndc_depth = clip_space_pos.z / clip_space_pos.w;
float depth = (((farZ-nearZ) * ndc_depth) + nearZ + farZ) / 2.0;

Projection matrix transforms 3D points in scene to 2D points on the viewport, converting eye space to clip space then NDC by dividing clip coordinates' w component.

For Orthographic Projection, eye space coordinates are directly mapped to NDC.


Orthographic Projection

Z-component calculated using the linear function:

z_ndc = z_eye * -2/(f-n) - (f+n)/(f-n)


Perspective Projection

Z-component calculated using the rational function:

z_ndc = ( -z_eye * (f+n)/(f-n) - 2*f*n/(f-n) ) / -z_eye


Ensure the Z-coordinate of the circle in orthographic projection lies between depths of objects in perspective projection. Depth value formula is depth = z_ndc * 0.5 + 0.5, calculations can be done using NDC instead of depth values.

Normalized device coordinates found using the project method of THREE.PerspectiveCamera. The unproject converts NDC to view space then world space.

Example:

// JavaScript code snippet
var renderer, pScene, oScene, pCam, oCam, frontPlane, backPlane, circle;
// Initialization, rendering, and animation functions...
init();
animate();

// CSS style snippet
html,body {
    height: 100%;
    width: 100%;
    margin: 0;
    overflow: hidden;
}
// Load Three.js library
<script src="https://threejs.org/build/three.min.js"></script>

OpenGL - Mouse coordinates to Space coordinates

How to render depth linearly in modern OpenGL with gl_FragCoord.z in fragment shader?

Answer №2

I have discovered a unique method that utilizes only the perspective camera to adjust the size of adornments based on their distance from the camera. While it is akin to a solution mentioned in another response to a similar query, my approach has its own distinct characteristics. My specific challenge involves not just maintaining consistent adornment sizes regardless of distance, but also exerting precise control over their dimensions on screen.

To ensure they are scaled to an exact size rather than any arbitrary dimension, I implement a function for calculating on-screen size as detailed in this referenced answer. By computing the position of both ends of a vector with a known on-screen length and evaluating the projection distance to the screen, I can determine the precise scaling factor:

var widthVector = new THREE.Vector3( 100, 0, 0 );
widthVector.applyEuler(pCam.rotation);

var baseX = getScreenPosition(circle, pCam).x;
circle.position.add(widthVector);
var referenceX = getScreenPosition(circle, pCam).x;
circle.position.sub(widthVector);

var scale = 100 / (referenceX - baseX);
circle.scale.set(scale, scale, scale);

An issue with this approach is that while it typically yields accurate results, occasional rounding errors may cause adornments to render incorrectly.

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

The module 'three/examples/jsm/controls/OrbitControls' or its corresponding type declarations could not be located

In my React project, I am utilizing TypeScript and three.js, where I'm importing OrbitControls in the following manner: import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; How ...

Replace a portion of text with a RxJS countdown timer

I am currently working on integrating a countdown timer using rxjs in my angular 12 project. Here is what I have in my typescript file: let timeLeft$ = interval(1000).pipe( map(x => this.calcTimeDiff(orderCutOffTime)), shareReplay(1) ); The calcTim ...

Can an additional height be added to the equalizer to increase its height?

Is it feasible to append an additional 35px to the height determined by Foundation Equalizer? For instance, if Equalizer computes a height of 350px, I would like to increase it by 35px. This means that the resultant style should be height: 385px; instead ...

Introducing React JSX via a nifty bookmarklet

Looking to convert code found in an HTML file into a bookmarklet? Here's the code snippets involved: <script src="JSXTransformer-0.13.3.js"></script> <script src="react-0.13.3.js"></script> <script type="text/jsx;harmony=tr ...

Creating a distinct folder for every upload in Node.js using multer and shortid

Utilizing shortid for unique IDs for each upload along with multer for the post handler. I am uploading some input data as well as an image and aiming to store each upload inside "upload/XXXXX". The unique ID is generated in the app.post(...) and I am see ...

Output the JSON array containing [object Object] and [object Object]

Is there a way to transform [object Object],[object Object] into something like "[[{ 'x': '1', 'y': '0' }, { 'x': '2', 'y': '1' }]]"; using javascript? ...

Creating a slider plugin with Right-to-Left (RTL) support

Despite multiple requests being ignored by the developer to support RTL on PhotoSwipe, I have decided to take matters into my own hands. Reversing the array order was a simple task: self.items = !!(_options.rtl) ? _items = items.reverse() : _items = item ...

Dealing with bodyParser errors in Express.js

Whenever I try to upload an image that exceeds my 5mb limit, I get hit with a payload error. Is there a better way to handle payload errors, like sending JSON or HTML responses instead? PayloadTooLargeError: request entity too large at readStream (D:&b ...

Displaying imported JSON data in a tabular format

I am struggling with writing a function in my VueJS template that sends a request to the backend API with a specific key and then displays the JSON response in a table. For example, here is a sample of the JSON data: {"driver_id":1,"driver_name":"{driver ...

Tips for creating text that adjusts to the size of a div element

I'm currently working on developing my e-commerce website. Each product is showcased in its own separate div, but I've encountered a challenge. The issue arises when the product description exceeds the limits of the div, causing it to overflow ou ...

Guide to correctly setting up the b-table component in vue-test-utils

Currently, I am utilizing the buefy css library in conjunction with the vue.js framework. The challenge I am facing involves unit testing my vue component (Foo), which includes the b-table component from buefy: <b-table :data="foo" class=&quo ...

Retrieve the outcome of an Ajax request

Hey there, I have a situation where I need to submit a form and make an ajax call. Here's what I'm doing: $('#pForm').on('submit', function(e) { e.preventDefault(); //Prevents default submit var form = $(this); var post ...

Prevent submission until all form fields are filled with data

I need help fixing the issue I'm having with disabling the submit button until all input fields have some data. Currently, the button is disabled but remains that way even after all inputs are filled in. What could be causing this problem? $(docume ...

Arrange the array in chronological order based on the month and year

I'm looking for assistance with sorting an array by month and year to display on a chart in the correct order. Array1: ['Mar19','Apr18','Jun18','Jul18','May18','Jan19'....]; Desired Output: ...

Is there a way to identify the top five elements that are most frequently occurring in the array?

I've successfully found the element that appears the most in an array, but now I'm facing a new challenge where I need to identify the top 5 elements that appear most frequently. Here is the array: [ "fuji", "acros", &q ...

Switching between sockets on a rotational basis

Imagine there is a division in the HTML file, and an interesting game is being played where each player has the opportunity to change the color. However, there’s a twist – they can only switch colors after the other player has taken their turn. The pla ...

What are the best practices for managing live notifications with WebSocket technology?

I have developed a real-time chat application in React.js with Socket.io, but I want to implement a new feature. Currently, User A and User B can only communicate if they both have the chat open. I would like to notify User B with a popup/notification wh ...

Error: The function `map` is not available on the variable `filtredItems

Can anyone please help me out? I tried creating a simple code to filter items based on an input value, but I encountered an ERROR :( even though I am confident that I followed the tutorial steps correctly. You can find the tutorial here This is the compl ...

Combining switch statements from various classes

Is there a way to merge switch statements from two different classes, both with the same function name, into one without manually overriding the function or copying and pasting code? Class A: protected casesHandler(): void { switch (case){ ...

Important notice: It is not possible to assign refs to function components. Any attempt to do so will result in failure. If you intended to assign a ref, consider

My console is showing a warning when I use the nextJs Link component. Can someone assist me in resolving this issue and providing an explanation? Here is the message from the console: https://i.stack.imgur.com/jY4FA.png Below is a snippet of my code: im ...