在webGL中记录FPS

4
我可以帮您进行翻译。以下是您需要翻译的内容:

我正在尝试比较移动设备上3D应用程序的性能。我在WebGL中设置了一个3D太阳系,并试图记录或至少显示FPS。到目前为止,这就是我所拥有的:

在body标签内:

<script language="javascript">
var x, message;
x = Time;
message = "fps is equal to ";
document.write (message); // prints the value of the message variable
document.write (x); //prints the value of x
</script>

在canvas的绘制函数中获取时间变量,我有以下代码:
var Time = 0;
function drawScene() {
var startTime = new Date();
//draw scene here
var endTime = new Date();
Time = (endTime - startTime)
}

在画布底部得到的输出是“fps is equal to null”。任何帮助都将是极好的!

1
请查看 https://github.com/mrdoob/stats.js/。 - gaitat
5个回答

18

显示FPS非常简单,与WebGL无关,除了通常希望知道它之外。这是一个小的FPS显示。

const fpsElem = document.querySelector("#fps");

let then = 0;
function render(now) {
  now *= 0.001;                          // convert to seconds
  const deltaTime = now - then;          // compute time since last frame
  then = now;                            // remember time for next frame
  const fps = 1 / deltaTime;             // compute frames per second
  fpsElem.textContent = fps.toFixed(1);  // update fps display
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
<div>fps: <span id="fps"></span></div>

使用requestAnimationFrame进行动画,因为这是它的用途。浏览器可以与屏幕刷新同步,以提供润滑流畅的动画效果。如果您的页面不可见,他们也可以停止处理。另一方面,setTimeout并非为动画设计,不会与浏览器的页面绘制同步。
您应该不要使用Date.now()来计算FPS,因为Date.now()只返回毫秒。而且,使用(new Date()). getTime()尤其糟糕,因为它每帧都会生成一个新的日期对象。
requestAnimationFrame已经传递了自页面加载以来的微秒时间,所以只需使用它即可。
还有一种常见方法是跨帧平均FPS。

const fpsElem = document.querySelector("#fps");
const avgElem = document.querySelector("#avg");

const frameTimes = [];
let   frameCursor = 0;
let   numFrames = 0;   
const maxFrames = 20;
let   totalFPS = 0;

let then = 0;
function render(now) {
  now *= 0.001;                          // convert to seconds
  const deltaTime = now - then;          // compute time since last frame
  then = now;                            // remember time for next frame
  const fps = 1 / deltaTime;             // compute frames per second
  
  fpsElem.textContent = fps.toFixed(1);  // update fps display
  
  // add the current fps and remove the oldest fps
  totalFPS += fps - (frameTimes[frameCursor] || 0);
  
  // record the newest fps
  frameTimes[frameCursor++] = fps;
  
  // needed so the first N frames, before we have maxFrames, is correct.
  numFrames = Math.max(numFrames, frameCursor);
  
  // wrap the cursor
  frameCursor %= maxFrames;
    
  const averageFPS = totalFPS / numFrames;

  avgElem.textContent = averageFPS.toFixed(1);  // update avg display
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { font-family: monospace; }
<div>        fps: <span id="fps"></span></div>
<div>average fps: <span id="avg"></span></div>


3
尽管你的回答大部分都很好,但有一点很重要却不正确:用帧时间(deltaTime)除以1并不是计算每秒帧数的正确方法。你应该像被采纳的答案那样计算在一秒钟内渲染的帧数。 - HankMoody
如果一个帧需要0.9秒,而59个帧只需要0.00169秒,如果你只看每秒绘制的帧数,你是看不到这个差别的。 - gman
当然你会看到这点。使用帧时间为0.00169秒时,应用程序的渲染速度为600 FPS。因此,如果现在一帧需要0.9秒,那么帧计数器将计算60帧,而不是600帧,因此你会看到10倍的差异。帧计数就像你的averageFPS,但变量更少,代码更简单。 - HankMoody
1
这个 const fps = 1 / deltaTime; 只是一种希望,如果所有帧都在那个时间内渲染,我们可以拥有多少FPS的想法。 - HankMoody
顺便说一句,WebGL基础知识真的很棒! :) - HankMoody
就我个人而言,我只是想测量每帧之间的时间以用于角色速度,而且我已经在使用requestAnimationFrame,所以这对我来说非常完美。我不知道它已经发送了一个时间属性! - MonsterBasket

6

我猜你在重复调用drawScene,但如果你只设置了x一次,那么每次调用drawScene时它就不会更新。另外,你存储在Time中的是经过的时间而不是每秒帧数。

下面这个怎么样?想法是计算渲染的帧数,一旦过去一秒钟,就将其存储在fps变量中。

<script>
var elapsedTime = 0;
var frameCount = 0;
var lastTime = 0;

function drawScene() {

   // draw scene here

   var now = new Date().getTime();

   frameCount++;
   elapsedTime += (now - lastTime);

   lastTime = now;

   if(elapsedTime >= 1000) {
       fps = frameCount;
       frameCount = 0;
       elapsedTime -= 1000;

       document.getElementById('test').innerHTML = fps;
   }
}

lastTime = new Date().getTime();
setInterval(drawScene,33);

</script>

<div id="test">
</div>

4
不要使用setInterval进行动画。当选项卡隐藏时,它会导致用户的计算机遭受DoS攻击。请使用requestAnimationFrame。参见http://paulirish.com/2011/requestanimationframe-for-smart-animating/ - gman
不用谢!也可以看看 @gman 的回答,以及使用 requestAnimationFrame 来实现更好的动画效果。 - Barış Uşaklı
elapsedTime -= 1000; 应该修改为 elapsedTime = 0; 吗?在我做出这个改变之前,它无法正常工作。 - d13

0
我创建了一个面向对象版本的Barış Uşaklı的答案。它还可以跟踪过去一分钟的平均fps。 用法: 全局变量:
var fpsCounter;

在程序启动时的某个地方创建对象:
 fpsCounter = new FpsCounter();

在你的draw()函数中调用update方法并更新fps显示:
function drawScene() {          
  fpsCounter.update();
  document.getElementById('fpsDisplay').innerHTML = fpsCounter.getCountPerSecond();
  document.getElementById('fpsMinuteDisplay').innerHTML = fpsCounter.getCountPerMinute();
  // Code   
}

注意:我只是为了简单起见,在绘制函数中放置了fps-display更新。在60fps的情况下,它每秒钟设置60次,即使每秒一次也足够了。 FpsCounter 代码:
function FpsCounter(){
    this.count = 0;
    this.fps = 0;
    this.prevSecond;  
    this.minuteBuffer = new OverrideRingBuffer(60);
}

FpsCounter.prototype.update = function(){
    if (!this.prevSecond) {     
        this.prevSecond = new Date().getTime();
            this.count = 1;
    }
    else {
        var currentTime = new Date().getTime();
        var difference = currentTime - this.prevSecond;
        if (difference > 1000) {      
            this.prevSecond = currentTime;
            this.fps = this.count; 
            this.minuteBuffer.push(this.count);
            this.count = 0;
        }
        else{
            this.count++;
        }
    }    
};

FpsCounter.prototype.getCountPerMinute = function(){
    return this.minuteBuffer.getAverage();
};

FpsCounter.prototype.getCountPerSecond = function(){
    return this.fps;
};

覆盖缓冲区代码:

function OverrideRingBuffer(size){
    this.size = size;
    this.head = 0;
    this.buffer = new Array();
};

OverrideRingBuffer.prototype.push = function(value){      
    if(this.head >= this.size) this.head -= this.size;    
    this.buffer[this.head] = value;
    this.head++;
};

OverrideRingBuffer.prototype.getAverage = function(){
    if(this.buffer.length === 0) return 0;

    var sum = 0;    

    for(var i = 0; i < this.buffer.length; i++){
        sum += this.buffer[i];
    }    

    return (sum / this.buffer.length).toFixed(1);
};

0

由于其他答案都没有涉及到问题中的"在WebGL中"部分,因此在正确测量WebGL中FPS时,我将添加以下重要细节。

window.console.time('custom-timer-id');    // start timer

/* webgl draw call here */                 // e.g., gl.drawElements();

gl.finish();                               // ensure the GPU is ready

window.console.timeEnd('custom-timer-id'); // end timer

为了简单起见,我使用了控制台计时器。我试图表明始终使用WebGLRenderingContext.finish()以确保正确的时间测量,因为所有对GPU的WebGL调用都是异步的!


1
这行代码行不通。在WebGL中,gl.finish并不像你想象的那样工作。即使在OpenGL中,这也不能给你准确的结果。 - gman

0

使用旋转数组可以更好地处理。 带有DOM元素:

<div id="fps">

以下脚本可以解决问题:

var fpsLastTick = new Date().getTime();
var fpsTri = [15, 15, 15]; // aims for 60fps

function animate() {
  // your rendering logic blahh blahh.....


  // update fps at last
  var now = new Date().getTime();
  var frameTime = (now - fpsLastTick);
  fpsTri.shift(); // drop one
  fpsTri.push(frameTime); // append one
  fpsLastTick = now;
  fps = Math.floor(3000 / (fpsTri[0] + fpsTri[1] + fpsTri[2])); // mean of 3
  var fpsElement = document.getElementById('fps')
  if (fpsElement) {
    fpsElement.innerHTML = fps;
  }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接