使用three.js将FPS保持在一定范围内

时间:2017-12-06 18:24:39

标签: javascript performance three.js

使用WebGLRenderer创建antialias = true的实例会导致明显的性能问题,因为分辨率会增加,尤其是视网膜显示(window.devicePixelRatio === 2)。

由于无法动态改变抗锯齿模式,问题是:如何自动调整像素比率以保持FPS高于某个阈值(例如30)?

1 个答案:

答案 0 :(得分:0)

我们的想法是在渲染循环中监控FPS(通过测量requestAnimationFrame次呼叫之间的间隔)并相应地减少或增加DPR。

“监控”是指将这些间隔记录到阵列中,删除最小值/最大值(以避免峰值),取平均值并将其与预定义阈值进行比较。

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;

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

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

  // accumulator is not fully filled
  if (frequencyAccumulator.length < accumulatorLength) {
    return;
  }

  // an update happened recently
  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;
}

请注意,更新DPR可能会导致短时动画冻结。

此外,可以使用更聪明的东西来平衡DPR值(而不是用updateDpr()的线性步骤调用0.1,例如二分搜索。