计算游戏每秒的帧数


110

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

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

Answers:


100

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

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

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


14
然后,为了确保安全性和整洁性,您可能需要浮动重量比例= 0.1; 和时间=时间*(1.0-weightRatio)+ last_frame * weightRatio
korona

2
原则上听起来不错,也很简单,但是实际上这种方法的平滑性几乎没有引起注意。不好。
Petrucio

1
@Petrucio,如果平滑度太低,只需提高时​​间常数(weightRatio = 0.05、0.02、0.01 ...)
John Dvorak

8
@Petrucio:last_frame不表示(或至少不应该表示)上一帧的持续时间;它应该表示time您为最后一帧计算的值。这样,将包括所有以前的帧,而最新的帧将被加权最大。
j_random_hacker 2013年

1
变量名称“ last_frame”具有误导性。“ current_frame”将更具描述性。同样重要的是要知道示例中的变量“ time”必须是全局变量(即在所有帧中保持其值),理想情况下是浮点数。它包含平均值/汇总值,并在每一帧上进行更新。比率越高,将“时间”变量确定为一个值(或使其远离该值)所需的时间就越长。
jox 2015年

52

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

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

我非常喜欢这种方法。将MAXSAMPLES设置为100的任何特定原因?
Zolomon

1
这里的MAXSAMPLES是为了得出fps值而求平均值的值的数量。
科里·格罗斯

8
它是简单的移动平均线(SMA)
KindDragon 2012年

很好,谢谢!我在游戏中对其进行了调整,从而使滴答功能无效,并且另一个函数返回了FPS,即使渲染代码未显示FPS,我也可以在每个滴答处运行主要的FPS。
TheJosh

2
请使用模数而不是if。tickindex = (tickindex + 1) % MAXSAMPLES;
Felix K.

25

好吧,当然

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


12

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

就是 每3秒获取一次counter / 3,然后清除该计数器。


+1尽管这只会在间隔内为您提供一个新值,但这很容易理解,不需要数组也不需要猜测值,并且在科学上是正确的。
opatut 2012年

10

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


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

  • 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,渲染最后一帧所花费的时间以及已获得的帧数呈现。


1
+1为第二种方法。我想这对于超级精确计算将是一件好事:3
zeboidlund

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

2

好答案在这里。实施方式取决于所需的功能。我更喜欢上面那个家伙自己的“时间=时间* 0.9 + last_frame * 0.1”。

但是,我个人更喜欢将平均价格偏重于更新数据,因为在游戏中,最难以压榨也是最受关注的就是SPIKES。因此,我将使用更像.7 \ .3的分割将使尖峰显示得更快(尽管其效果也会更快地从屏幕上掉下来。请参阅下文)

如果您专注于渲染时间,那么.9.1拆分效果很好,因为b / c往往更平滑。尽管对于游戏/ AI /物理峰值来说,更需要关注的是尖峰,因为尖峰通常会使您的游戏显得不连贯(假设我们不低于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问题,从而在帧结束时解决它们。


我喜欢time = time * 0.9 + last_frame * 0.1使显示平滑变化的平均计算。
Fabien Quatravaux 2012年

2

一个比使用大量旧帧速率更好的系统是执行以下操作:

new_fps = old_fps * 0.99 + new_fps * 0.01

与旧的帧速率相比,此方法使用的内存少得多,所需的代码少得多,并且在最近的帧速率上更重要,同时仍然可以消除突然的帧速率变化带来的影响。


1

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


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

1

这是一个使用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

将计数器设置为零。每次绘制一帧,计数器都会增加。每秒打印一次计数器。泡沫,冲洗,重复。如果您想获得额外的积分,请保持一个计数器不变,然后除以总秒数即可得出平均值。


0

在伪代码(类似于c ++)中,这两个是我在工业图像处理应用程序中使用的应用程序,该应用程序必须处理来自一组外部触发相机的图像。“帧速率”的变化具有不同的来源(皮带上的生产速度变慢或变快),但问题是相同的。(我假设您有一个简单的timer.peek()调用,从应用程序启动或上次调用以来,您得到的像是msec的nr(nsec?)。

解决方案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
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

我该怎么做!

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
不会推荐给任何人。将滴答声总数除以秒数总数会使FPS接近数学极限,在很长一段时间后基本上会稳定在2-3个值上,并显示不准确的结果。
卡蒂克·楚

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
}

0

在Typescript中,我使用此算法来计算帧率和帧时间平均值:

let getTime = () => {
    return new Date().getTime();
} 

let frames: any[] = [];
let previousTime = getTime();
let framerate:number = 0;
let frametime:number = 0;

let updateStats = (samples:number=60) => {
    samples = Math.max(samples, 1) >> 0;

    if (frames.length === samples) {
        let currentTime: number = getTime() - previousTime;

        frametime = currentTime / samples;
        framerate = 1000 * samples / currentTime;

        previousTime = getTime();

        frames = [];
    }

    frames.push(1);
}

用法:

statsUpdate();

// Print
stats.innerHTML = Math.round(framerate) + ' FPS ' + frametime.toFixed(2) + ' ms';

提示:如果样本为1,则结果为实时帧速率和帧时间。


0

这基于KPexEA的答案,并给出了简单移动平均线。整理并转换为TypeScript以方便复制和粘贴:

变量声明:

fpsObject = {
  maxSamples: 100,
  tickIndex: 0,
  tickSum: 0,
  tickList: []
}

功能:

calculateFps(currentFps: number): number {
  this.fpsObject.tickSum -= this.fpsObject.tickList[this.fpsObject.tickIndex] || 0
  this.fpsObject.tickSum += currentFps
  this.fpsObject.tickList[this.fpsObject.tickIndex] = currentFps
  if (++this.fpsObject.tickIndex === this.fpsObject.maxSamples) this.fpsObject.tickIndex = 0
  const smoothedFps = this.fpsObject.tickSum / this.fpsObject.maxSamples
  return Math.floor(smoothedFps)
}

使用情况(可能因您的应用程序而异):

this.fps = this.calculateFps(this.ticker.FPS)

-1

存储开始时间并在每个循环中增加一次帧计数器?每隔几秒钟,您可以只打印framecount /(现在-开始时间),然后重新初始化它们。

编辑:哎呀。双重忍者

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.