ゲームの1秒あたりのフレーム数を計算する


111

ゲームの1秒あたりのフレーム数を計算するための適切なアルゴリズムは何ですか?画面の隅に数字で表示したい。最後のフレームをレンダリングするのにかかった時間だけを見ると、数値の変化が速すぎます。

回答が各フレームを更新し、フレームレートが増加する場合と減少する場合で異なって収束しない場合のボーナスポイント。

回答:


101

平滑化された平均が必要です。最も簡単な方法は、現在の回答(最後のフレームを描画する時間)を取得し、それを前の回答と組み合わせる方法です。

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

0.9 / 0.1の比率を調整することで、「時定数」を変更できます。これは、数値が変化にどのくらい速く応答するかを示します。古い回答を支持する大きな割合はゆっくりとした滑らかな変化を与え、新しい回答を支持する大きな割合はより速い変化値を与えます。明らかに、2つの要素を1つに追加する必要があります。


14
次に、ばか証明と整頓のために、おそらくfloat weightRatio = 0.1; そして、time = time *(1.0-weightRatio)+ last_frame * weightRatio
korona 2008

2
原理的にはシンプルで良さそうに聞こえますが、実際にはこのアプローチのスムージングは​​ほとんど目立ちません。駄目だ。
ペトルシオ

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

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の値を算出するために平均化される値の数です。
Cory Gross、

8
単純移動平均(SMA)
KindDragon 12/07/27

パーフェクト、ありがとう!私はゲームでそれを微調整したので、tick funcは無効になり、別の関数がFPSを返します。レンダーコードにFPSが表示されていない場合でも、ティックごとにメインの関数を実行できます。
TheJosh

2
ifではなくmoduloを使用してください。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これは間隔で新しい値を提供するだけですが、これは理解しやすく、配列も推測値も必要とせず、科学的に正しいです。
2012年

10

それには、少なくとも2つの方法があります。


1つ目は、他の人が私の前でここで言及したものです。私はそれが最も簡単で好ましい方法だと思います。あなただけを追跡する

  • 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
2番目の方法の場合は+1。私はそれが
非常に

5

これはほとんどの人にとってやり過ぎかもしれません。そのため、実装したときに投稿していませんでした。しかし、それは非常に堅牢で柔軟です。

キューには最後のフレーム時間を格納するため、最後のフレームを考慮するだけでなく、平均FPS値を正確に計算できます。

また、そのフレームの時間を人為的に台無しにすることがわかっていることを実行している場合、1つのフレームを無視することもできます。

また、実行時にキューに保存するフレーム数を変更できるため、最適な値をその場でテストできます。

// 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

ここで良い答え。どのように実装するかは、何のために必要かによって異なります。私は上記の人による移動平均1つ「時間=時間* 0.9 +最終フレーム* 0.1」を好んでいます。

しかし、私は個人的には、平均をより新しいデータに重きを置きたいと思っています。なぜなら、ゲームでは、スカッシュが最も難しく、私にとって最も興味深いスパイクだからです。したがって、.7 \ .3分割のようなものを使用すると、スパイクの表示がはるかに速くなります(ただし、効果が画面外にドロップする速度も速くなります。以下を参照してください)。

レンダリング時間に焦点を当てている場合、.9.1分割はかなりスムーズに機能します。ただし、ゲームプレイ/ 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秒のフレーム数として前の値を保存します)


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を使用した完全な例を次に示します(ただし、任意の言語に簡単に適応できます)。マーティンの答えの平滑化方程式を使用しているため、メモリのオーバーヘッドはほとんどなく、私はうまくいく値を選択しました(定数をいじって、ユースケースに適応してみてください)。

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

カウンターをゼロに設定します。フレームを描くたびに、カウンターが増加します。1秒ごとにカウンターを印刷します。泡立て、すすぎ、繰り返します。追加のクレジットが必要な場合は、実行中のカウンターを維持し、移動平均の合計秒数で割ります。


0

(c ++のような)疑似コードでは、これら2つは、外部トリガーカメラのセットからの画像を処理する必要があった産業用画像処理アプリケーションで使用したものです。「フレームレート」の変動にはさまざまな原因がありましたが(ベルトでの生成が遅いまたは速い)、問題は同じです。(私はあなたが単純な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
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の値に落ち着き、不正確な結果を表示します。
Kartik Chugh

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

開始時間を保存し、ループごとに1回フレームカウンターをインクリメントしますか?数秒ごとに、framecount /(Now-starttime)を出力して、それらを再初期化するだけです。

編集:おっと。ダブル忍者

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.