2008-09-17 238 views
97

在游戏中计算每秒帧数的好算法是什么?我想在屏幕的角落将其显示为一个数字。如果我只看看渲染最后一帧需要多长时间,数字变化太快。在游戏中计算每秒帧数

如果您的答案更新了每帧,并且在帧速率增加和减少时收敛不同,则为奖励点数。

回答

88

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

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

通过调整0.9/0.1的比例,您可以更改'时间常数' - 即数字响应变化的速度。赞成旧答案的较大比例给出了较慢的平滑变化,大部分赞成新答案的变化值更快。显然这两个因素必须加在一起!

+13

然后为了防呆和整齐,你可能想要类似 float weightRatio = 0.1; and time = time *(1.0 - weightRatio)+ last_frame * weightRatio – korona 2008-11-11 10:04:48

+1

原则上听起来很好,很简单,但实际上这种方法的平滑几乎没有引人注目。不好。 – Petrucio 2012-12-10 17:15:29

+1

@Petrucio如果平滑过低,只需调整时间常数(weightRatio = 0.05,0.02,0.01 ...) – 2013-05-18 17:44:03

11

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

即,每3秒钟,得到计数器/ 3,然后清除计数器。

0

将计数器设置为零。每次你画一个帧增加计数器。每秒钟后打印计数器。起泡,冲洗,重复。如果你想获得额外的奖励,请保留一个跑步计数器并除以运行平均值的总秒数。

1

你可以保留一个计数器,增加它每帧渲染之后,然后重置计数器,当你在一个新的第二(存储上次的值作为最后一秒的#帧渲染)

0

店开始时间和增加你的framecounter每个循环一次?每隔几秒钟您就可以打印framecount /(Now - starttime),然后重新初始化它们。

编辑:oops。双ninja'ed

22

嘛,肯定

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并将时间戳设置为当前时间。

42

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

#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); 
} 
0

在(C++等)的伪代码这两个是我在不得不从一组外部触发相机的处理图像工业图像处理应用中。 “帧率”的变化有不同的来源(带上生产速度较慢或较快),但问题是相同的。 (我假设你有一个简单的timer.peek()调用,让你从这个应用类似毫秒的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 
    } 
} 
2

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

不过我个人比较喜欢更多地走向新的数据压得我平均,因为在一场比赛中它是最难壁球尖峰,从而最感兴趣的我。所以我会使用更像.7 \ .3分割将使得秒杀显示速度更快(虽然它的效果会更快,以下屏幕也会下降)。请参阅下面的内容。

如果您的重点是渲染时间,那么.9.1分割工作得非常好,它往往更平滑。尽管游戏玩法/人工智能/物理尖峰更受关注,因为这通常会让你的游戏显得波涛汹汹(假设我们不低于20fps,通常会比低帧率更差)

因此,我还能做什么也加入这样的:

#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发出结束他们发生的框架。

9

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


首先是一个别人这里提到在我面前。 我认为这是最简单和首选的方式。你只跟踪

  • CN的:你有多少帧渲染
  • TIME_START的计数器:时间,因为你已经开始计数
  • TIME_NOW:当前时间

在这种情况下计算fps与评估此公式一样简单:

  • FPS = cn /(time_now - time_start)。

再有就是你可能会喜欢使用一些天的超级酷的方式:

比方说,你有“我”框架来考虑。我将使用这个表示法:f [0],f [1],...,f [i-1]来描述渲染帧0,帧1,...,帧(i-1) ) 分别。

Example where i = 3 

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

然后,I帧后帧每秒的数学定义将是

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

而同样的公式,但仅考虑I-1的帧。

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

现在,这里的窍门是在这样一种方式,它将包含公式(2)的右侧,并用它替换它的左侧上的修改公式(1)的右侧。

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

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,渲染最后一帧的持续时间以及渲染的帧数。

0
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; 
     } 
    } 

}); 
1

我该怎么做!

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)”。

0

这里是我如何做到这一点(在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 
} 
5

这可能是矫枉过正,对大多数人来说,这就是为什么我没有贴吧,当我实现它。但它非常强大和灵活。

它存储一个具有最后帧时间的队列,所以它可以准确地计算平均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); 
} 
0

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

new_fps = old_fps * 0.99 + new_fps * 0.01 

此方法使用少得多的内存,需要更少的代码,并把更多的重要性根据最近的帧率而不是旧的帧率,同时仍然平滑突然帧率变化的影响。

1

的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)); 
0

这里有一个完整的例子,使用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)