Three.js: dividing the camera's view frustum (similar to what is done in Cascade Shadow Mapping)

Discovering the intriguing world of Cascade Shadow Mapping has been quite insightful (check out this helpful tutorial I stumbled upon: link). I'm currently exploring how to implement a similar approach in Three.js, with the added advantage of having the 'light' source coincide with the camera position.

CSM recommends dividing the camera frustum into multiple segments and generating a distinct orthographic projection matrix for each segment. This process has been posing a challenge for me. Initially, I attempted to locate frustum corners with the following code:

function cameraToWorld(point, camera) {
    camera.updateWorldMatrix();
    return point.applyMatrix4(camera.matrixWorld);
}

function calculateCameraFrustumCorners(camera) {
    // Consider this camera as a PerspectiveCamera instance
    const hFOV = 2 * Math.atan(Math.tan(THREE.Math.degToRad(camera.fov) / 2) * camera.aspect);
    const xNear = Math.tan(hFOV / 2) * camera.near;
    const xFar = Math.tan(hFOV / 2) * camera.far;

    const yNear = Math.tan(THREE.Math.degToRad(camera.fov) / 2) * camera.near;
    const yFar = Math.tan(THREE.Math.degToRad(camera.fov) / 2) * camera.far;

    let arr = [new THREE.Vector3(xNear,      yNear,      camera.near),
               new THREE.Vector3(xNear * -1, yNear,      camera.near),
               new THREE.Vector3(xNear,      yNear * -1, camera.near),
               new THREE.Vector3(xNear * -1, yNear * -1, camera.near),
               new THREE.Vector3(xFar,       yFar,       camera.far),
               new THREE.Vector3(xFar  * -1, yFar,       camera.far),
               new THREE.Vector3(xFar,       yFar  * -1, camera.far),
               new THREE.Vector3(xFar  * -1, yFar  * -1, camera.far)];

    return arr.map(function (val) {
        return cameraToWorld(val, camera);
    });
}

Subsequently, I proceed to create a bounding box around the frustum corners and utilize it to form an OrthographicCamera:

function getOrthographicCameraForPerspectiveCamera(camera) {
    const frustumCorners = calculateCameraFrustumCorners(camera);

    let minX = Number.MAX_VALUE;
    let maxX = Number.MIN_VALUE;
    let minY = Number.MAX_VALUE;
    let maxY = Number.MIN_VALUE;
    let minZ = Number.MAX_VALUE;
    let maxZ = Number.MIN_VALUE;
    for (let i = 0; i < frustumCorners.length; i++) {
        let corner = frustumCorners[i];
        let vec = new THREE.Vector4(corner.x, corner.y, corner.z, 1);

        minX = Math.min(vec.x, minX);
        maxX = Math.max(vec.x, maxX);
        minY = Math.min(vec.y, minY);
        maxY = Math.max(vec.y, maxY);
        minZ = Math.min(vec.z, minZ);
        maxZ = Math.max(vec.z, maxZ);
    }

    return new THREE.OrthographicCamera(minX, maxX, minY, maxY, minZ, maxZ);
}

Following this step, I intend to pass the orthographic camera matrix to the shader and employ it for texture mapping (particularly the 'shadow map' texture, rendered using the orthographic camera obtained from the aforementioned function).

Unfortunately, this implementation isn't yielding the desired results: the bounding box coordinates obtained do not align well with the typical parameters expected for an orthographic camera setup, which usually consists of the format

width / -2, width / 2, height / 2, height / -2, 0.1, 1000
, diverging significantly from the output of the provided code. Is there a requirement for an additional transformation on the bounding box corners? Perhaps obtaining their coordinates in screen space rather than world space? My grasp of the distinct coordinate systems remains limited. Could it be an error in my calculation of frustum corners?

Answer №1

When it comes to different types of projections, the viewing volume varies. For example, in Orthographic projection, the viewing volume is a cuboid, while in Perspective projection, it becomes a Frustum, which is essentially a truncated pyramid.

Regardless of the projection type, in normalized device space, the viewing volume is represented as a cube with specific corner points. The left, lower, near corner is at (-1, -1, -1) and the right, top, far corner is at (1, 1, 1).

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


To determine the corner points of the viewing volume, you need to define the points in normalized device space and then transform them to world space. This transformation involves using the inverse projection matrix to convert from normalized device space to view space, and the inverse view matrix to convert from view space to world space:

let ndc_corners = [
    [-1,-1,-1], [1,-1,-1], [-1,1,-1], [1,1,-1],
    [-1,-1, 1], [1,-1, 1], [-1,1, 1], [1,1, 1]];

let view_corners = []
for (let i=0; i < ndc_corners.length; ++i) {
    let ndc_v = new THREE.Vector3(...ndc_corners[i]);
    view_corners.push(ndc_v.applyMatrix4(camera.projectionMatrixInverse));
}

let world_corners = []
for (let i=0; i < view_corners.length; ++i) {
    let view_v = view_corners[i].clone();
    world_corners.push(view_v.applyMatrix4(camera.matrixWorld));
}

In THREE.js, this process can be simplified by utilizing the Vector3.unproject( camera : Camera) method:

let ndc_corners = [
    [-1,-1,-1], [1,-1,-1], [-1,1,-1], [1,1,-1],
    [-1,-1, 1], [1,-1, 1], [-1,1, 1], [1,1, 1]];

let world_corners = []
for (let i=0; i < ndc_corners.length; ++i) {
    let ndc_v = new THREE.Vector3(...ndc_corners[i]);
    world_corners.push(ndc_v.unproject(camera));
}

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

Is it possible to utilize the returned value of a function within an if statement?

Is there a way to return the result of a function without needing to declare a variable? Can you return the result of a function in a single line? How can you return the result of a function inside an if statement? Is it possible to use a function's ...

Functionality of the Parameters Object

As I transition from using the params hash in Rails to learning Node/Express, I find myself confused about how it all works. The Express.js documentation provides some insight: 'This property is an array containing properties mapped to the named rout ...

The system displayed an 'Error' stating that the variable 'index' is defined in the code but is never actually utilized, resulting in the (no-unused-vars) warning

I have configured eslint and eslint-plugin-react for my project. Upon running ESLint, I am encountering no-unused-vars errors for every React component in the codebase. It seems like ESLint is not properly identifying JSX or React syntax. Any suggestions ...

Exploring the search feature within Redux and React-Native

The search functionality in redux is giving me some trouble. I have written the following code for it, but strangely enough, it's not working properly. Interestingly, the same code works perfectly when used without redux. searchItems: (name ) => ...

"Utilizing Jquery to extract data from a form field and combine it with a URL for maximum value

Today, I am delving into the world of jQuery for the first time and finding myself in need of some assistance with a particular problem. While this issue may seem trivial to experienced individuals, it is posing quite a challenge for me. The dilemma lies ...

Discovering distinct colors for this loading dots script

Looking for assistance with a 10 loading dots animation script. I'm trying to customize the color of each dot individually, but when I create separate divs for each dot and control their colors in CSS, it causes issues with the animation. If anyone ...

Eliminate the bottom border from the MUI Input component

Is there a way to get rid of the bottom-line borders on these input fields? I attempted using CSS but couldn't find a solution for this component -> <Input type={"text} /> ? https://i.sstatic.net/4QpWym.png ...

Troubleshooting: Imported Variable in Angular 2+ Throwing Module Not Found Error

During my testing process, I encountered an issue when trying to require a .json file with data to perform checks on. Despite passing the string indicating where to find the file into the require function, it seems to be unsuccessful... Success: const da ...

I am looking to develop a customizable table where the user can input their desired information

Looking to create an HTML page featuring a 10x10 table with alternating red and green squares. After loading the page, a pop-up window will prompt the user to input a word, which will then appear only in the red squares of the table. While I've succes ...

Setting up route handlers in Node.js on a pre-initialized HTTP server

Is there a way to add route handlers to an http server that is already instantiated? Most routers, including express, typically need to be passed into the http.createServer() method. For instance, with express: var server = http.createServer(app); My r ...

Tips for avoiding problems with quoting and using apostrophes in a JavaScript function inside a tag in a JSP file

Within my JSP, I have a string value stored in ${state.status.code} that I need to pass to a JavaScript function when a table element is clicked using onClick to trigger the showStatus function. Here is how I have attempted to achieve this: <c:set var= ...

Encountering an undefined response in API call using Fetch with Express Nuxt

I've hit a roadblock with a task involving Express, API, and Fetch. I'm utilizing Nuxt along with Shopify API endpoints to retrieve data such as orders. Below is my express API Endpoint which should return an array of objects (orders). const bod ...

Issues encountered when trying to implement helperText in mui date picker

Can someone assist with this issue? The helper text is not displaying as expected in the following code snippet: <div className={classes.container}> <LocalizationProvider dateAdapter={AdapterDateFns}> <Des ...

Resolving the "Error: Cannot update a React state on an unmounted component" issue

I encountered a console error while attempting to navigate to a new page within my web application. Here's the error message I received: Warning: A React state update was attempted on an unmounted component, which is essentially a no-op. However, t ...

The functionality of Vue is acting up with the HTML, unexpectedly refreshing when a button is clicked

I am currently experiencing an issue with my Vue application. When I click the button, it clears the input field (which it shouldn't) and doesn't perform any other actions. The variables "codigo" and "payload" do not display anything on the scree ...

It is not possible to use Date.now within passport.js

When creating a user via Facebook on my Node.js website using passport, I want to assign the register date. In my Mongoose Schema, I can use the following for the local provider: regisDate: { type: Date, default: Date.now }, However, when it co ...

Problems with Glitching and Distortions in the Web Audio API

Hey there! I recently delved into exploring the web audio API and put together a simple synthesizer to get familiar with its features. However, I've encountered an issue where the audio gets distorted when subjected to intense sound input. If anyone w ...

Issue: No default template engine specified and no file extension provided. (Using Express framework)

While I came across numerous questions with a similar title, they only provided me with partial help and did not completely resolve the error that plagued me. Just to provide some context, I had created a file named listing.js as a tool for running node c ...

jQuery uploadify encountered an error: Uncaught TypeError - It is unable to read the property 'queueData' as it is undefined

Once used seamlessly, but now facing a challenge: https://i.stack.imgur.com/YG7Xq.png All connections are aligned with the provided documentation $("#file_upload").uploadify({ 'method' : 'post', 'but ...

Inserting items into the document after updating the list with fresh data

I populate an array of objects when a button is clicked. The array only has 10 objects initially, but users can add more after it's loaded into the DOM. Here's how I handle this: $scope.Information = []; $.each(data, function (i, v) { if ...