Maintaining optimal frames per second using three.js

When using a WebGLRenderer instance with antialias = true, performance problems become evident as the resolution increases, particularly on retina displays (window.devicePixelRatio === 2).

As it is not feasible to switch antialiasing modes dynamically, the query arises: how can we automatically modify the pixel ratio in an attempt to maintain FPS above a specific threshold (e.g. 30)?

Answer №1

The concept involves tracking FPS within the rendering loop (by measuring intervals between requestAnimationFrame calls) and adjusting DPR (Device Pixel Ratio) accordingly.

When we say "tracking", it means storing these intervals in an array, filtering out extreme values to prevent spikes, calculating an average, and comparing it against predefined thresholds.

const highFrequencyThreshold = 20; // ~50 FPS
const lowFrequencyThreshold = 34;  // ~30 FPS

const minDpr = 0.5;
const maxDpr = window.devicePixelRatio;
const deltaDpr = 0.1;

const relaxPeriod = 4000;
const accumulatorLength = 20;

const frameTimestamp = performance.now();
const frequencyAccumulator = [];
const lastUpdatedAt = null;

const renderer = new WebGLRenderer({
  antialias: true,
});

renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

animate();

function animate(timestamp: number = performance.now()) {
  requestAnimationFrame(animate);

  monitor(frameTimestamp, timestamp);
  frameTimestamp = timestamp;

  // additional animation calculations and rendering
  // ...
}

function monitor(frameTimestamp: number, now: number) {
  collectFrequency(now - frameTimestamp);

  // if the accumulator is not yet full
  if (frequencyAccumulator.length < accumulatorLength) {
    return;
  }

  // a recent update has occurred
  if (now - lastUpdatedAt < relaxPeriod) {
    return;
  }

  const dpr = renderer.getPixelRatio();
  const frequencyMedian = median(...frequencyAccumulator);

  if (frequencyMedian > lowFrequencyThreshold && dpr > minDpr) {
    updateDpr(dpr, -deltaDpr, now);
  } else if (frequencyMedian < highFrequencyThreshold && dpr < maxDpr) {
    updateDpr(dpr, deltaDpr, now);
  }
}

function collectFrequency(frequency: number) {
  if (frequency > 0) {
    frequencyAccumulator.push(frequency);
  }

  if (frequencyAccumulator.length > accumulatorLength) {
    frequencyAccumulator.shift();
  }
}

function updateDpr(dpr: number, delta: number, now: number) {
  renderer.setPixelRatio(dpr + delta);
  frequencyAccumulator = [];
  lastUpdatedAt = now;
}

function median(...elements: number[]): number {
  const indexOfMin = elements.indexOf(Math.min(...elements));
  const indexOfMax = elements.indexOf(Math.max(...elements));
  const noMinMax = elements.filter((_, index) => index !== indexOfMin && index !== indexOfMax);

  return average(...noMinMax);
}

function average(...elements: number[]): number {
  return elements.reduce((sum, value) => sum + value, 0) / elements.length;
}

Keep in mind that adjusting the DPR can lead to temporary animation stuttering.

Additionally, a more intelligent method could be implemented for fine-tuning the DPR value instead of using a linear step of 0.1, such as employing a bisectional search technique.

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

Adjust the size of an element in response to changes in the size of

I've implemented a jQuery function to resize an element dynamically based on window size changes: $(window).resize(function() { topHeight = $("#top").height(); height = $(window).height() - 210; $("#container").height(height); $("#g").height( ...

How to implement a timeout feature in JavaScript/TypeScript for cloud functions

I'm currently facing an issue with trying to delay certain actions using Cloud Firestore. Despite my attempts, the setTimeout/setInterval functions don't seem to be working as expected in my code. export const onTimerCreate = functions.firestore ...

When items are removed client-side, the Listbox becomes null

Given a Web Forms project inherited by me, I am relatively new to the field of Web development. The page in question features 2 listboxes: lstCaseLoad, containing "Caseloads" (ID numbers), and lstAssignedCaseLoad, filled with Caseloads chosen by the Form U ...

Transferring information and storing it in a textbox

I have a homepage that features a popup window. <textarea class="form-control item"></textarea> <button type="button" class="btn btn-primary" name="name">Send</button> Additionally, there is a secondary page at (/conclusion/main) ...

What steps are needed to set up React SPA authentication using Keycloak and the PKCE flow?

This is my first time diving into the world of Keycloak. I have successfully set up a Keycloak instance on my local machine where I can create realms, clients, and more. I've come across several examples of using React with Keycloak, but none of them ...

Is there a way to modify the color of my question post-submission?

Within my code, there are numerous queries that need to be addressed. Upon submission, I desire to alter the color of each question to green if the response is correct, or red if it is incorrect. document.getElementById("submit-button").addEventLi ...

I am facing difficulty creating a dropdown menu with nested options that only appear when the user hovers over the main selection

Here are the parent options I have and the code I have attempted to build. I would like to include sub-options such as phones, radios, speakers under the electronics parent option and many more. <select id="category" name="category" class="dropDown"& ...

By setting `queue: false` when calling jQuery's `show()` method, you can

When looking at the code below, it is clear that even though showLoader is the first call, the loader does not appear immediately. This delay is due to the fact that heavyLifting function is blocking the UI thread. function onClick() { showLoader(); ...

Guide to integrating a static page with personalized CSS and JavaScript resources within a Rails project

Currently, I am developing a simple Rails application (using Rails version 4.1) and I am looking to incorporate a static page into it. The static page is structured as follows: | |- index.html |- css folder |-- (various css files) |- js folder |-- (some j ...

The Google Maps marker is not accurately displaying the designated location

While working on my project, I successfully integrated Google Maps. However, I have encountered a problem: when I search for a specific location, the marker is not displaying at the correct point, but rather somewhere else. The latitude and longitude value ...

The function dispatch is not recognized and will be removed from the database. An error will be generated indicating that dispatch is not a valid function

There seems to be an issue with the delete function in Ticket Actions as it is giving an error that dispatch is not a function. The goal here is to enable users to delete specific tickets by clicking on them and also provide an option to edit the ticket. ...

What is the best way to create a button with this functionality?

In the form that I have created, it is opened in a bootstrap modal style. This form contains a button that, when clicked, triggers an alert box to appear. The code snippet used for this functionality is as follows: echo "<script>"; echo "alert(&apos ...

Error: Attempting to access the 'style' property of a non-existent element(index):110 onclick

Having trouble with my alert box - I'm trying to add a cross that allows the user to close it by clicking on it: <div id="alert"> <img src="cross.png" onclick="document.getElementById(alert).style.display = 'none'" width="15px" hei ...

When attempting to transfer data to a CSV file from my Firebase database, I encounter an issue where the

I am facing an issue with exporting data from my Firebase Firestore to a .csv file. I have followed all the necessary steps, but whenever I try to add the values for export, they show up as undefined. While I am not an expert in React and consider myself ...

Add a npm module without type definitions

I am currently utilizing Typescript version 2.1 and facing an issue with installing an npm package called 'reactable' that lacks typings. When attempting to import the package using import * as Reactable from 'reactable', Typescript di ...

The problem of IE being interrupted by Ajax requests

Currently facing an issue with IE7 that doesn't occur in Chrome or Firefox: I have multiple page elements being loaded through ajax. These elements are dynamically displayed on the page as hyperlinks to another page. As these elements load, I can som ...

Managing JSON data retrieval and manipulation techniques

My code is set up to display the image, title, and summary for all entries in a JSON file. However, I only want to display the image, title, and summary for the first entry, and only show the title for the rest of the entries. Please advise. <html> ...

Utilize specific Angular JS methods just a single time

In my Angular application, I have the following architecture: Index Page -> Shell Page -> User view (User can open subview from here) Every route change in my application goes through the Shell page. There is a function on the Shell page called act ...

Divide and store parts in an array

Is there a method to split a string at a specific character and include that character in the resulting array? For instance, if we split the string "hello ??? world" at ???, the resulting array would be ["hello ", "???", "world"]. It's worth noting ...

"Troubleshooting: My Three.js scene is displaying blank, what could

I recently created a simple example using Three.js, but my code was not organized into classes which caused some issues with displaying anything in the view. Here is an example of the code I used: HTML file <!DOCTYPE html> <html> <head> ...