代码之家  ›  专栏  ›  技术社区  ›  Tod

计算游戏中每秒的帧数

  •  100
  • Tod  · 技术社区  · 16 年前

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

    如果你的答案更新了每一帧,并且当帧速率增加或减少时收敛的方式不同,则会得到额外的分数。

    17 回复  |  直到 7 年前
        1
  •  93
  •   Martin Beckett    9 年前

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

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

    通过调整0.9/0.1的比率,你可以改变“时间常数”,也就是数字对变化的反应速度。支持旧答案的分数越大,变化越慢;支持新答案的分数越大,变化越快。显然这两个因素必须加在一起!

        2
  •  44
  •   Axalo David    7 年前

    这是我在很多比赛中使用的。

    #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);
    }
    
        3
  •  22
  •   Wedge    16 年前

    好吧,当然

    frames / sec = 1 / (sec / frame)
    

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

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

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

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

    渲染完新帧后,会将新的渲染时间排队,并将旧的渲染时间排队。或者,只有当渲染时间的总和超过某个预设值(例如1秒)时,才能出列。您可以维护“last fps value”和一个last updated timestamp,以便在需要时触发更新fps图。尽管如果格式一致,使用移动平均值,在每帧上打印“瞬时平均”fps可能是可以的。

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

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

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

        4
  •  11
  •   apandit    16 年前

    每次呈现屏幕时递增一个计数器,并在一段时间间隔内清除该计数器以测量帧速率。

    每隔3秒,取计数器/3,然后清空计数器。

        5
  •  9
  •   Peter Jankuliak    13 年前

    至少有两种方法:


    第一个是其他人在我面前提到的。 我认为这是最简单和最可取的方法。你只是想知道

    • CN:与渲染的帧数相对应
    • 时间开始:从你开始计数开始的时间
    • 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帧后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,渲染最后一帧的持续时间和渲染的帧数。

        6
  •  5
  •   Petrucio    9 年前

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

    它存储一个具有最后一帧时间的队列,因此它可以准确地计算平均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);
    }
    
        7
  •  2
  •   David Frenkel    16 年前

    好答案。你如何实现它取决于你需要它做什么。我更喜欢上面那个人写的“时间=时间*0.9+最后一帧*0.1”的跑步平均成绩。

    不过,我个人更喜欢将我的平均值更重于更新的数据,因为在游戏中,尖峰是最难压扁的,因此对我最感兴趣。所以我会使用一些更像.7\.3分割的东西会使尖峰更快地出现(尽管它的效果也会更快地从屏幕上消失)。见下文)

    如果你的注意力集中在渲染时间上,那么.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发出它们发生的帧的结尾。

        8
  •  2
  •   Barry Smith    9 年前

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

    new_fps = old_fps * 0.99 + new_fps * 0.01
    

    此方法使用的内存少得多,所需的代码少得多,并且比旧的帧速率更重视最近的帧速率,同时还能平滑突然的帧速率变化的影响。

        9
  •  1
  •   Mike Stone    16 年前

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

        10
  •  1
  •   BottleFact    12 年前

    我是怎么做到的!

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

        11
  •  1
  •   Ephellon Grey    8 年前

    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));
    
        12
  •  0
  •   Bryan Oakley    16 年前

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

        13
  •  0
  •   jilles de wit    16 年前

    在(C++类)伪代码中,这两个是我在工业图像处理应用中所使用的,它们必须处理来自外部触发相机的图像。“帧速率”的变化具有不同的来源(在带上生产速度较慢或更快),但问题是相同的。(我假设您有一个简单的timer.peek()调用,它给您提供了类似于nr of msec(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
       }
    } 
    
        14
  •  0
  •   Totty.js    12 年前
    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;
            }
        }
    
    });
    
        15
  •  0
  •   adventurerOK    11 年前

    以下是我在爪哇的做法:

    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
    }
    
        16
  •  0
  •   jd20    7 年前

    下面是一个完整的例子,使用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)
    
        17
  •  -1
  •   Jimmy    16 年前

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

    编辑:OOPS。双忍者