it-swarm.cn

计算游戏中的每秒帧数

什么是计算游戏中每秒帧数的好算法?我想将它显示为屏幕一角的数字。如果我只看一下渲染最后一帧所花费的时间,那么数字变化太快了。

如果您的答案更新每一帧,并且在帧速率增加而不是减少时不会收敛,则奖励积分。

102
Tod

您需要平滑的平均值,最简单的方法是获取当前答案(绘制最后一帧的时间)并将其与之前的答案相结合。

// eg.
float smoothing = 0.9; // larger=more smoothing
measurement = (measurement * smoothing) + (current * (1.0-smoothing))

通过调整0.9/0.1比率,您可以更改“时间常数” - 即数字响应变化的速度。支持旧答案的较大部分给出了较慢的平滑变化,有利于新答案的很大一部分提供了更快的变化值。显然这两个因素必须加到一个!

93
Martin Beckett

这是我在很多游戏中使用的。

#define MAXSAMPLES 100
int tickindex=0;
int ticksum=0;
int ticklist[MAXSAMPLES];

/* need to zero out the ticklist array before starting */
/* average will ramp up until the buffer is full */
/* returns average ticks per frame over the MAXSAMPLES last frames */

double CalcAverageTick(int newtick)
{
    ticksum-=ticklist[tickindex];  /* subtract value falling off */
    ticksum+=newtick;              /* add new value */
    ticklist[tickindex]=newtick;   /* save new value so it can be subtracted later */
    if(++tickindex==MAXSAMPLES)    /* inc buffer index */
        tickindex=0;

    /* return average */
    return((double)ticksum/MAXSAMPLES);
}
44
KPexEA

嗯,当然

frames / sec = 1 / (sec / frame)

但是,正如您所指出的,呈现单个帧所需的时间有很多变化,并且从UI角度来看,以帧速率更新fps值根本不可用(除非数字非常稳定)。

你想要的可能是移动平均线或某种分级/重置计数器。

例如,您可以维护一个队列数据结构,该结构保留最后30,60,100或者您拥有的帧中每个帧的渲染时间(您甚至可以设计它,以便在运行时调整限制)。要确定合适的fps近似值,您可以确定队列中所有渲染时间的平均fps:

fps = # of rendering times in queue / total rendering time

完成渲染新帧后,您将新渲染时间排队并使旧渲染时间出列。或者,只有当渲染时间的总和超过某个预设值(例如1秒)时,才可以出列。您可以保持“最后的fps值”和最后更新的时间戳,这样您就可以触发何时更新fps数字,如果您愿意的话。虽然如果你有一致的格式,你可以在每帧上打印“瞬时平均”fps。

另一种方法是使用重置计数器。保持精确(毫秒)的时间戳,帧计数器和fps值。完成渲染帧后,递增计数器。当计数器达到预设限制(例如100帧)或自时间戳超过某个预设值(例如1秒)以来的时间时,计算fps:

fps = # frames / (current time - start time)

然后将计数器重置为0并将时间戳设置为当前时间。

22
Wedge

每次渲染一个屏幕时增加一个计数器,并在您想要测量帧速率的某个时间间隔内清除该计数器。

IE浏览器。每3秒钟,获取计数器/ 3然后清除计数器。

11
apandit

至少有两种方法可以做到:


第一个是我之前提到的其他人。我认为这是最简单和首选的方式。你只是要跟踪

  • cn:计算你渲染了多少帧
  • time_start:自你开始计算以来的时间
  • time_now:当前时间

在这种情况下计算fps就像评估这个公式一样简单:

  • FPS = cn /(time_now - time_start)。

那么有一天你可能想要使用超酷的方式:

假设你有'i'帧需要考虑。我将使用这种表示法:f [0],f [1],...,f [i-1]来描述渲染帧0,帧1,...,帧(i-1)所需的时间) 分别。

Example where i = 3

|f[0]      |f[1]         |f[2]   |
+----------+-------------+-------+------> time

然后,在i帧之后的fps的数学定义将是

(1) fps[i]   = i     / (f[0] + ... + f[i-1])

和相同的公式,但只考虑i-1帧。

(2) fps[i-1] = (i-1) / (f[0] + ... + f[i-2]) 

现在的诀窍是修改公式(1)的右侧,使其包含公式(2)的右侧,并将其替换为左侧。

就像这样(如果你把它写在纸上,你应该更清楚地看到它):

fps[i] = i / (f[0] + ... + f[i-1])
       = i / ((f[0] + ... + f[i-2]) + f[i-1])
       = (i/(i-1)) / ((f[0] + ... + f[i-2])/(i-1) + f[i-1]/(i-1))
       = (i/(i-1)) / (1/fps[i-1] + f[i-1]/(i-1))
       = ...
       = (i*fps[i-1]) / (f[i-1] * fps[i-1] + i - 1)

所以根据这个公式(虽然我的数学推导技巧有点生疏),要计算新fps,你需要知道前一帧的fps,渲染最后一帧所用的持续时间以及你的帧数。渲染。

9
Peter Jankuliak

这对大多数人来说可能有点过头了,这就是我实施它时没有发布的原因。但它非常强大和灵活。

它存储具有最后帧时间的队列,因此它可以比仅考虑最后一帧更准确地计算平均FPS值。

它还允许你忽略一个框架,如果你正在做一些你知道会人为地搞砸那个框架时间的东西。

它还允许您在运行时更改要在队列中存储的帧数,因此您可以即时测试它对您来说最有价值。

// Number of past frames to use for FPS smooth calculation - because 
// Unity's smoothedDeltaTime, well - it kinda sucks
private int frameTimesSize = 60;
// A Queue is the perfect data structure for the smoothed FPS task;
// new values in, old values out
private Queue<float> frameTimes;
// Not really needed, but used for faster updating then processing 
// the entire queue every frame
private float __frameTimesSum = 0;
// Flag to ignore the next frame when performing a heavy one-time operation 
// (like changing resolution)
private bool _fpsIgnoreNextFrame = false;

//=============================================================================
// Call this after doing a heavy operation that will screw up with FPS calculation
void FPSIgnoreNextFrame() {
    this._fpsIgnoreNextFrame = true;
}

//=============================================================================
// Smoothed FPS counter updating
void Update()
{
    if (this._fpsIgnoreNextFrame) {
        this._fpsIgnoreNextFrame = false;
        return;
    }

    // While looping here allows the frameTimesSize member to be changed dinamically
    while (this.frameTimes.Count >= this.frameTimesSize) {
        this.__frameTimesSum -= this.frameTimes.Dequeue();
    }
    while (this.frameTimes.Count < this.frameTimesSize) {
        this.__frameTimesSum += Time.deltaTime;
        this.frameTimes.Enqueue(Time.deltaTime);
    }
}

//=============================================================================
// Public function to get smoothed FPS values
public int GetSmoothedFPS() {
    return (int)(this.frameTimesSize / this.__frameTimesSum * Time.timeScale);
}
5
Petrucio

这里的答案很好。你如何实现它取决于你需要它。我更喜欢上面的那个人自己的“时间=时间* 0.9 + last_frame * 0.1”。

然而,我个人喜欢将我的平均值更重地放在更新的数据上,因为在游戏中,最难点的是SPIKES,因此对我来说最感兴趣。所以我会使用更像.7\.3分割的东西会让秒杀显示得更快(尽管它的效果会在屏幕外快速下降..见下文)

如果您的重点是RENDERING时间,那么.9.1分割效果非常好b/c它往往更平滑。对于游戏玩法/人工智能/物理飙升来说更令人担忧,因为这通常会让你的游戏看起来不稳定(假设我们没有低于20 fps,这通常比低帧速率更差)

那么,我要做的还是添加这样的东西:

#define ONE_OVER_FPS (1.0f/60.0f)
static float g_SpikeGuardBreakpoint = 3.0f * ONE_OVER_FPS;
if(time > g_SpikeGuardBreakpoint)
    DoInternalBreakpoint()

(用你发现的不可接受的峰值填充3.0f)这将让你找到并因此 解决 FPS发出它们发生的帧的结束。

2
David Frenkel

一个比使用大量旧帧速率更好的系统就是做这样的事情:

new_fps = old_fps * 0.99 + new_fps * 0.01

这种方法使用更少的内存,需要更少的代码,并且比旧的帧速率更重视最近的帧速率,同时仍然平滑突发帧速率变化的影响。

2
Barry Smith

您可以保留一个计数器,在每个帧渲染后递增计数器,然后在您使用新的秒时重置计数器(将前一个值存储为最后一秒的渲染帧数)

1
Mike Stone

我是怎么做到的!

boolean run = false;

int ticks = 0;

long tickstart;

int fps;

public void loop()
{
if(this.ticks==0)
{
this.tickstart = System.currentTimeMillis();
}
this.ticks++;
this.fps = (int)this.ticks / (System.currentTimeMillis()-this.tickstart);
}

用语言来说,刻度时钟跟踪刻度。如果是第一次,则需要当前时间并将其置于“tickstart”中。在第一个滴答之后,它使变量'fps'等于滴答时钟的滴答数除以时间减去第一个滴答的时间。

Fps是一个整数,因此是“(int)”。

1
BottleFact

JavaScript的:

// Set the end and start times
var start = (new Date).getTime(), end, FPS;
  /* ...
   * the loop/block your want to watch
   * ...
   */
end = (new Date).getTime();
// since the times are by millisecond, use 1000 (1000ms = 1s)
// then multiply the result by (MaxFPS / 1000)
// FPS = (1000 - (end - start)) * (MaxFPS / 1000)
FPS = Math.round((1000 - (end - start)) * (60 / 1000));
1
Ephellon Dantzler

这是一个完整的例子,使用Python(但很容易适应任何语言)。它使用Martin的答案中的平滑方程,因此几乎没有内存开销,我选择了适合我的值(随意使用常量来适应您的用例)。

import time

SMOOTHING_FACTOR = 0.99
MAX_FPS = 10000
avg_fps = -1
last_tick = time.time()

while True:
    # <Do your rendering work here...>

    current_tick = time.time()
    # Ensure we don't get crazy large frame rates, by capping to MAX_FPS
    current_fps = 1.0 / max(current_tick - last_tick, 1.0/MAX_FPS)
    last_tick = current_tick
    if avg_fps < 0:
        avg_fps = current_fps
    else:
        avg_fps = (avg_fps * SMOOTHING_FACTOR) + (current_fps * (1-SMOOTHING_FACTOR))
    print(avg_fps)
0
jd20

将计数器设置为零。每次绘制一个框架增加计数器。每秒打印一次计数器。泡沫,冲洗,重复。如果您想要额外的积分,请保持一个正在运行的计数器并除以运行平均值的总秒数。

0
Bryan Oakley
qx.Class.define('FpsCounter', {
    extend: qx.core.Object

    ,properties: {
    }

    ,events: {
    }

    ,construct: function(){
        this.base(arguments);
        this.restart();
    }

    ,statics: {
    }

    ,members: {        
        restart: function(){
            this.__frames = [];
        }



        ,addFrame: function(){
            this.__frames.Push(new Date());
        }



        ,getFps: function(averageFrames){
            debugger;
            if(!averageFrames){
                averageFrames = 2;
            }
            var time = 0;
            var l = this.__frames.length;
            var i = averageFrames;
            while(i > 0){
                if(l - i - 1 >= 0){
                    time += this.__frames[l - i] - this.__frames[l - i - 1];
                }
                i--;
            }
            var fps = averageFrames / time * 1000;
            return fps;
        }
    }

});
0
Totty.js

在(c ++ like)伪代码中,这两个是我在工业图像处理应用程序中使用的,它们必须处理来自一组外部触发的摄像机的图像。 “帧速率”的变化有不同的来源(带上的生产更慢或更快),但问题是相同的。 (我假设你有一个简单的timer.peek()调用,它给你类似msec(nsec?)的nr,因为应用程序启动或最后一次调用)

解决方案1:快速但不更新每一帧

do while (1)
{
    ProcessImage(frame)
    if (frame.framenumber%poll_interval==0)
    {
        new_time=timer.peek()
        framerate=poll_interval/(new_time - last_time)
        last_time=new_time
    }
}

解决方案2:每帧更新,需要更多内存和CPU

do while (1)
{
   ProcessImage(frame)
   new_time=timer.peek()
   delta=new_time - last_time
   last_time = new_time
   total_time += delta
   delta_history.Push(delta)
   framerate= delta_history.length() / total_time
   while (delta_history.length() > avg_interval)
   {
      oldest_delta = delta_history.pop()
      total_time -= oldest_delta
   }
} 
0
jilles de wit

这是我如何做到的(用Java):

private static long ONE_SECOND = 1000000L * 1000L; //1 second is 1000ms which is 1000000ns

LinkedList<Long> frames = new LinkedList<>(); //List of frames within 1 second

public int calcFPS(){
    long time = System.nanoTime(); //Current time in nano seconds
    frames.add(time); //Add this frame to the list
    while(true){
        long f = frames.getFirst(); //Look at the first element in frames
        if(time - f > ONE_SECOND){ //If it was more than 1 second ago
            frames.remove(); //Remove it from the list of frames
        } else break;
        /*If it was within 1 second we know that all other frames in the list
         * are also within 1 second
        */
    }
    return frames.size(); //Return the size of the list
}
0
adventurerOK