What is the mechanism behind the clipping function in the three.js frustum?

Currently, I am exploring the frustum feature of three.js by making changes to the example provided in the Scene Graph section here.

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 40;
  const aspect = 2;  
  const near = 0.1;
  const far = 1000;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.set(0, 50, 0);
  camera.up.set(0, 0, 1);
  camera.lookAt(0, 0, 0);

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 3;
    const light = new THREE.PointLight(color, intensity);
    scene.add(light);
  }

  // an array of objects who's rotation to update
  const objects = [];

  const radius = 1;
  const widthSegments = 6;
  const heightSegments = 6;
  const sphereGeometry = new THREE.SphereGeometry(
      radius, widthSegments, heightSegments);

  const sunMaterial = new THREE.MeshPhongMaterial({emissive: 0xFFFF00});
  const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
  sunMesh.scale.set(5, 5, 5);
  scene.add(sunMesh);
  objects.push(sunMesh);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    objects.forEach((obj) => {
      obj.rotation.y = time;
    });

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
html, body {
  height: 100%;
  margin: 0;
}
#c {
  width: 100%;
  height: 100%;
  display: block;
}
<canvas id="c"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.144.0/three.min.js"></script>

Current Settings:

  • A sphere positioned at the origin with radius = 1.
  • A camera placed at y = 50, facing towards the origin, with UP direction as (0,0,1).
  • The frustum has parameters fov = 40, aspect = 2, near = 0.1, and far = 1000.

I adjusted two values:

  • near: 0.1 → 48
  • far: 1000 → 52.

Despite these changes, when observing the result, the top part of the sphere appears truncated. Why does this happen?

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

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 40;
  const aspect = 2; 
  const near = 48;
  const far = 52;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.set(0, 50, 0);
  camera.up.set(0, 0, 1);
  camera.lookAt(0, 0, 0);

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 3;
    const light = new THREE.PointLight(color, intensity);
    scene.add(light);
  }

  // an array of objects who's rotation to update
  const objects = [];

  const radius = 1;
  const widthSegments = 6;
  const heightSegments = 6;
  const sphereGeometry = new THREE.SphereGeometry(
      radius, widthSegments, heightSegments);

  const sunMaterial = new THREE.MeshPhongMaterial({emissive: 0xFFFF00});
  const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
  sunMesh.scale.set(5, 5, 5);
  scene.add(sunMesh);
  objects.push(sunMesh);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    objects.forEach((obj) => {
      obj.rotation.y = time;
    });

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
html, body {
  height: 100%;
  margin: 0;
}
#c {
  width: 100%;огоое
  height: 100%;
  display: block;
}
<canvas id="c"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.144.0/three.min.js"></script>

Answer №1

Grateful for the insight provided by WestLangley's comment, I have uncovered the solution: the Object3D.scale property is a 3D vector that locally scales the object. Mesh is actually a subclass of Object3D. In the revised example above, the "sphere" (which is actually a hexagon) is scaled up by a factor of 5. By commenting out this line, the default scaling (1×) reveals that the "sphere" fits inside the frustum.

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 10;
  const aspect = 2; // the canvas default
  const near = 49;
  const far = 51;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.set(0, 50, 0);
  camera.up.set(0, 0, 1);
  camera.lookAt(0, 0, 0);

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 3;
    const light = new THREE.PointLight(color, intensity);
    scene.add(light);
  }

  // an array of objects whose rotation needs updating
  const objects = [];

  const radius = 1;
  const widthSegments = 6;
  const heightSegments = 6;
  const sphereGeometry = new THREE.SphereGeometry(
    radius, widthSegments, heightSegments
  );

  const sunMaterial = new THREE.MeshPhongMaterial({emissive: 0xFFFF00});
  const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
  //sunMesh.scale.set(5, 5, 5);
  scene.add(sunMesh);
  objects.push(sunMesh);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    objects.forEach((obj) => {
      obj.rotation.y = time;
    });

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
html, body {
  height: 100%;
  margin: 0;
}
#c {
  width: 100%;
  height: 100%;
  display: block;
}
<canvas id="c"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.144.0/three.min.js"></script>

In order to validate my understanding, I decreased the distance between near and the center of the "sphere" by 0.3, resulting in the expected gap observed.

function main() {
  const canvas = document.querySelector('#c');
  const renderer = new THREE.WebGLRenderer({canvas});

  const fov = 10;
  const aspect = 2; // the canvas default
  const near = 49.3;
  const far = 50.7;
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  camera.position.set(0, 50, 0);
  camera.up.set(0, 0, 1);
  camera.lookAt(0, 0, 0);

  const scene = new THREE.Scene();

  {
    const color = 0xFFFFFF;
    const intensity = 3;
    const light = new THREE.PointLight(color, intensity);
    scene.add(light);
  }

  // an array of objects whose rotation needs updating
  const objects = [];

  const radius = 1;
  const widthSegments = 6;
  const heightSegments = 6;
  const sphereGeometry = new THREE.SphereGeometry(
    radius, widthSegments, heightSegments
  );

  const sunMaterial = new THREE.MeshPhongMaterial({emissive: 0xFFFF00});
  const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
  //sunMesh.scale.set(5, 5, 5);
  scene.add(sunMesh);
  objects.push(sunMesh);

  function resizeRendererToDisplaySize(renderer) {
    const canvas = renderer.domElement;
    const width = canvas.clientWidth;
    const height = canvas.clientHeight;
    const needResize = canvas.width !== width || canvas.height !== height;
    if (needResize) {
      renderer.setSize(width, height, false);
    }
    return needResize;
  }

  function render(time) {
    time *= 0.001;

    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement;
      camera.aspect = canvas.clientWidth / canvas.clientHeight;
      camera.updateProjectionMatrix();
    }

    objects.forEach((obj) => {
      obj.rotation.y = time;
    });

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();
html, body {
  height: 100%;
  margin: 0;
}
#c {
  width: 100%;
  height: 100%;
  display: block;
}
<canvas id="c"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.144.0/three.min.js"></script>

Note: I am addressing my own query to remove it from the list of unanswered questions.

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

One login for accessing multiple forms

I am trying to figure out a way to use one login for two different forms that serve different functions. How can I pass the login details between these two functions? Just to clarify, I only have knowledge of JavaScript and VBScript, not jQuery. For inst ...

Discovering sub-routes within a React application with react-router-dom Version 6

Currently, I am utilizing react-router-dom V6 which includes both Routes and useRoute. The primary routes on the site are structured as follows: export default function App() { return ( <Routes> <Route path="/" element={< ...

Guide to updating user input on the screen when a click event occurs using Javascript and HTML

I've been working on writing HTML and JavaScript code that enables user input to change based on which button the user clicks. The idea is that the user enters find/replace values, and when they hit the replace button, the text they initially entered ...

Struggling to locate the element value in Puppeteer? Reach out for assistance with await page.waitForSelector() or await page.waitForXPath()

insert image description here[ await page.waitForSelector('.DateRangeSelectorstyles__DateInput-sc-5md5uc-2.kXffji.sc-cSHVUG.VGFsW'); await page.type('.DateRangeSelectorstyles__DateInput-sc-5md5uc-2.kXffji.sc-cSHVUG.VGFsW','01- ...

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

Guide on adding a three.js html file into a flask template

I am currently developing a website with flask and I am looking to incorporate a 3D model into the design. I have found some code that utilizes three.js for this purpose, however, I am facing difficulties in integrating this code into my flask template. B ...

JavaScript cannot validate all things; it only validates a singular function

I need help with validating two functions at once. Currently, I am able to navigate to 'success.html' after entering the correct information for the form (name, subject, and number), but the radio buttons remain unchecked. I believe this issue ...

Is it possible to import multiple versions of a JavaScript file using HTML and JavaScript?

After extensive research, I am starting to think that using multiple versions of the same JavaScript library on a single page is not possible (without utilizing jquery Can I use multiple versions of jQuery on the same page?, which I am not). However, I wan ...

Choose the data from the initial entry to the second entry within the database

I am currently working on a project that requires me to retrieve dates from a database. The goal is to replace outdated dates with newer ones as they pass. Unfortunately, I am unsure how to achieve this and would greatly appreciate any assistance. It see ...

"Maximizing Heroku's Potential: Merging Node.js and Java Buildpacks for

Our project consists of an HTML5/JavaScript frontend that interacts with a Java backend through REST. The JavaScript component is built on top of Backbone.js rather than Node.js. Currently, the application is combined into one and deployed on Heroku using ...

Ways to detect events even after the object has been swapped out in JavaScript

I am currently developing a JavaScript-based Scrabble game and encountering a puzzling issue. The problem arises when tiles are generated within a tile rack div using a specific function, and an event listener is set up to respond to clicks on the tile div ...

The curious case of React and Redux: Why won't my value show up from the reducer even with mapStateToProps?

While working on my react project, I successfully integrated redux and set up a reducer containing an object with a key-value pair: "key: 'hello'". After importing it into my index.js file located in the reducers folder, I attempted to utilize it ...

Questions about clarifying JS promises and async-await functions

After doing some reading on promises in JavaScript, I have come across conflicting information which has left me with a few basic questions. I have two specific questions that need clarification: Is it necessary for every function in JavaScript to be ca ...

The HTML file seems to be having trouble connecting to the JavaScript file

I am currently working on a small, fun website project and have an HTML file along with a JavaScript file in the same directory. However, my HTML seems to be unaware of the existence of my JavaScript file. It fails to execute the function during the load e ...

Avoid HTML code injection in an ExtJS4 grid by properly escaping the href attribute

I am facing an issue with escaping HTML in my Extjs grid. I have used the following configuration : return Ext.String.htmlEncode(value); In the renderer of my column, this method works perfectly for simple HTML tags like h1, b, i, etc. However, if I in ...

Implementing the History API to dynamically load content into a div using Ajax

On my WordPress site, I have post content that loads into a div using AJAX when a user clicks on it. Now, I'd like to update the URL for the current post without using hashes. How can I achieve this with my JavaScript? JavaScript: jQuery(docum ...

Tips on allowing the backend file (app.js) to handle any URL sent from the frontend

In my Express app, I have two files located in the root directory: index.js and index.html. Additionally, there is a folder named "server" which contains a file named app.js that listens on port 3000. When running index.html using Live Server on port 5500 ...

Is there a way to extract data from the Redux state in a React component?

Seeking assistance with making an API request from Redux, followed by saving the data in my React state (arrdata). Although the API call is successful, I am facing challenges updating the App.js state based on the Redux API call. Any suggestions or insig ...

Functions properly in JSFiddle, but fails in the browser even when surrounded by a document ready handler

I am struggling with printing a 16x16 grid of divs using jQuery. It works perfectly on JSFiddle, but only one row is printed when testing in the browser. I realized that changing the jQuery version from edge to 2.1.3 on JSFiddle results in the same issue i ...

save the received data to a new document

How can I pass the data returned from the API below to another function in a separate JavaScript file? This question may be similar to others, but those do not involve working with returned results. Please consider this before labeling it as a duplicate. ...