关于算法:计算游戏中的每秒帧数

关于算法:计算游戏中的每秒帧数

Calculating frames per second in a game

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

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


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

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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);
}

好吧,当然

1
frames / sec = 1 / (sec / frame)

但是,正如您所指出的,渲染单个帧所需的时间有很多差异,并且从UIangular看,根本无法以帧速率更新fps值(除非数字非常大)稳定)。

您想要的可能是移动平均线或某种装仓/重置计数器。

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

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

当完成渲染新帧时,您需要排队新的渲染时间,并出队旧的渲染时间。或者,您只能在渲染时间的总和超过某个预设值(例如1秒)时才出队。您可以维护"最后fps值"和最后更新的时间戳,以便您可以根据需要触发何时更新fps数字。尽管如果采用一致的移动平均格式,但在每帧上打印"瞬时平均" fps可能没问题。

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

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

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


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

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


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

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

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

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

  • FPS = cn /(time_now-time_start)。

然后有一天您可能会想使用的超级酷的方法:

假设您要考虑" i"帧。我将使用以下表示法:f [0],f [1],...,f [i-1]描述渲染帧0,帧1,...,帧(i-1 )。

1
2
3
4
Example where i = 3

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

然后,i帧后fps的数学定义为

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

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

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

现在,这里的技巧是修改公式(1)的右侧,使其包含公式(2)的右侧,并用其替代左侧。

就像这样(如果您将其写在纸上,则应该更清楚地看到它):

1
2
3
4
5
6
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,渲染最后一帧所花费的时间以及帧数您已经渲染。


这对于大多数人来说可能是过大的杀伤力,这就是为什么我在实现它时没有发布它。但是它非常健壮和灵活。

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

如果您正在做某些已知会人为地弄乱该帧时间的事情,它还允许您忽略一帧。

它还允许您在队列运行时更改要存储在队列中的帧数,因此您可以即时对其进行测试以找出最适合您的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 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.9 last_frame * 0.1"。

不过,我个人更喜欢对平均数据加权以获取新数据,因为在游戏中,最难以压榨也是我最感兴趣的SPIKES。因此,我将使用更像.7 \\\\ .3拆分的东西将使尖峰显示得更快(尽管它的效果也将更快地从屏幕上掉下来。请参见下文)

如果您专注于渲染时间,那么.9.1拆分效果很好,因为b / c往往更平滑。尽管对于游戏/ AI /物理峰值来说,更需要关注峰值,因为这通常会使您的游戏显得不稳定(假设我们不低于20 fps,这通常比低帧速率更糟糕)

因此,我要做的是还添加如下内容:

1
2
3
4
#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问题,从而解决问题发生在帧结束时的问题。


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

1
new_fps = old_fps * 0.99 + new_fps * 0.01

与旧的帧速率相比,此方法使用更少的内存,更少的代码,并且对新帧速率的重视程度更高,同时仍可消除突然的帧速率变化的影响。


JavaScript:

1
2
3
4
5
6
7
8
9
10
11
// 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));

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)

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


我该怎么做!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)"。


这是根据KPexEA的回答给出的简单移动平均线。整理并转换为TypeScript以方便复制和粘贴:

变量声明:

1
2
3
4
5
6
fpsObject = {
  maxSamples: 100,
  tickIndex: 0,
  tickSum: 0,
  tickList: []
}

功能:

1
2
3
4
5
6
7
8
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)
}

用法(可能因您的应用程序而异):

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

这是我的操作方式(在Java中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
}

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

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

1
2
3
4
5
6
7
8
9
10
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
   }
}

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);
}

用法:

1
2
3
4
statsUpdate();

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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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;
        }
    }

});

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

edit:哎呀。双忍者


推荐阅读

    linux命令行显示路径?

    linux命令行显示路径?,系统,数据,信息,命令,工作,时间,标准,文件,目录,名称,l

    linux显示详细命令?

    linux显示详细命令?,工作,系统,信息,地址,命令,标准,数据,目录,文件,名称,执

    linux满屏显示命令?

    linux满屏显示命令?,工具,系统,服务,电脑,网络,技术,信息,数据,上会,软件,如

    显示linux时间命令?

    显示linux时间命令?,时间,系统,信息,一致,命令,文件,终端,目录,选项,参数,lin

    linux命令刷新显示?

    linux命令刷新显示?,系统,工作,最新,地址,命令,异常,分析,工具,信息,软件,Lin

    linux显示最多的命令?

    linux显示最多的命令?,系统,情况,信息,数据,工具,电脑,状态,时间,分析,命令,

    linux命令高亮显示?

    linux命令高亮显示?,系统,信息,命令,电脑,地址,代码,情况,分析,位置,文件,Lin

    重启计算机命令linux?

    重启计算机命令linux?,系统,工作,命令,服务,标准,设备,灵活,首要,意义,参数,L

    linux显示隐藏命令?

    linux显示隐藏命令?,系统,电脑,档案,工具,一致,生产,文件夹,文件,命令,开头,l

    linux命令匹配数字?

    linux命令匹配数字?,数字,档案,位置,环境,名字,较大,系统,权限,命令,用户,Lin

    linux显示错误命令?

    linux显示错误命令?,信息,系统,电脑,状态,时间,环境,命令,搜狐,密码,异常,虚

    linux逐行显示命令?

    linux逐行显示命令?,标准,信息,系统,工作,地址,命令,实时,名称,文件,目录,Lin

    linux打印屏幕命令?

    linux打印屏幕命令?,信息,系统,工作,标准,地址,命令,工具,状态,设备,网络,我

    linux权限数字命令?

    linux权限数字命令?,数字,系统,地址,权限,命令,标准,情况,管理,基础,文件,lin

    linux计算机的命令?

    linux计算机的命令?,系统,工作,信息,设备,技术,命令,网站,管理,灵活,基础,lin

    linux显示时间命令?

    linux显示时间命令?,时间,系统,管理,标准,信息,单位,工具,数据,中国,命令,lin

    linux启动显示命令行?

    linux启动显示命令行?,系统,密码,终端,状态,首页,情况,基础,电脑,信息,工具,l

    linux启动显示命令行?

    linux启动显示命令行?,系统,密码,终端,状态,首页,情况,基础,电脑,信息,工具,l

    linux显示之前的命令?

    linux显示之前的命令?,系统,信息,命令,地址,服务,环境,数据,标准,数字,不了,l

    linux打开显示器命令?

    linux打开显示器命令?,信息,工具,系统,环境,发行,实时,数据,设备,命令,文件,L