ゲームの1秒あたりのフレーム数を計算するための優れたアルゴリズムは何ですか?画面の隅に数字で表示したいです。最後のフレームをレンダリングするのにかかった時間を見ると、数値の変化が速すぎます。
答えは各フレームを更新し、フレームレートが増加する場合と減少する場合に異なる方法で収束しない場合のボーナスポイントです。
平滑化された平均が必要です。最も簡単な方法は、現在の回答(最後のフレームを描画する時間)を取得し、前の回答と結合することです。
// eg.
float smoothing = 0.9; // larger=more smoothing
measurement = (measurement * smoothing) + (current * (1.0-smoothing))
0.9/0.1の比率を調整することで、「時定数」を変更できます。これは、数値が変化に応答する速さです。古い回答を支持する割合が大きくなると、変化が遅くなり、新しい回答が優先される割合が大きくなると、値が速く変化します。明らかに、2つの要素は1つに追加する必要があります!
これは私が多くのゲームで使用したものです。
#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);
}
まあ、確かに
frames / sec = 1 / (sec / frame)
ただし、指摘するように、単一フレームのレンダリングにかかる時間には多くのばらつきがあり、UIの観点からは、フレームレートでfps値を更新することはまったく使用できません(数値が非常に安定している場合を除く)。
必要なのは、おそらく移動平均またはある種のビニング/リセットカウンターです。
たとえば、最後の30、60、100、またはwhat-have-youフレームのそれぞれのレンダリング時間を保持するキューデータ構造を維持できます(実行時に制限を調整できるように設計することもできます)。適切なfps近似値を決定するには、キュー内のすべてのレンダリング時間から平均fpsを決定できます。
fps = # of rendering times in queue / total rendering time
新しいフレームのレンダリングが終了したら、新しいレンダリング時間をエンキューし、古いレンダリング時間をデキューします。または、レンダリング時間の合計が事前設定値(1秒など)を超えた場合にのみ、キューから取り出すことができます。 「最後のfps値」と最後に更新されたタイムスタンプを維持して、必要に応じてfpsの数値を更新するタイミングをトリガーできます。一貫したフォーマットを使用している場合、移動平均を使用しても、各フレームに「瞬時平均」fpsを印刷しても大丈夫でしょう。
もう1つの方法は、リセットカウンターを持つことです。正確な(ミリ秒)タイムスタンプ、フレームカウンター、およびfps値を維持します。フレームのレンダリングが終了したら、カウンターを増やします。カウンターが事前に設定された制限(例:100フレーム)に達したとき、またはタイムスタンプからの時間が事前に設定された値を超えたとき(例:1秒)、fpsを計算します。
fps = # frames / (current time - start time)
次に、カウンターを0にリセットし、タイムスタンプを現在の時刻に設定します。
画面をレンダリングするたびにカウンタをインクリメントし、フレームレートを測定する一定の時間間隔でそのカウンタをクリアします。
すなわち。 3秒ごとに、counter/3を取得してから、カウンターをクリアします。
それを行うには、少なくとも2つの方法があります。
1つ目は、他の人がここで言及したものです。私はそれが最も簡単で好ましい方法だと思います。あなただけを追跡する
この場合のfpsの計算は、次の式を評価するのと同じくらい簡単です。
それから、いつか使いたいと思う格好いい方法があります:
考慮すべき「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])
ここでのコツは、式(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、最後のフレームをレンダリングするのにかかった時間、およびフレームの数を知る必要がありますレンダリングされます。
これはほとんどの人にとってはやり過ぎかもしれません。そのため、実装時に投稿していませんでした。しかし、非常に堅牢で柔軟です。
最後のフレーム時間でキューを保存するため、最後のフレームを考慮するだけでなく、平均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);
}
ここで良い答えです。それをどのように実装するかは、必要なものに依存します。私は、上記の男による「時間=時間* 0.9 + last_frame * 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を埋めます)これにより、solve FPSがフレームの終わりを発行します。
古いフレームレートの大きな配列を使用するよりもはるかに優れたシステムは、次のようなことをすることです:
new_fps = old_fps * 0.99 + new_fps * 0.01
この方法は、メモリの使用量がはるかに少なく、必要なコードがはるかに少なく、古いフレームレートよりも最近のフレームレートを重視する一方で、突然のフレームレート変更の影響を滑らかにします。
カウンターを保持し、各フレームがレンダリングされた後に増分し、新しい秒になったらカウンターをリセットできます(前の値をレンダリングされた最後の秒のフレームとして保存します)
// 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の答えで平滑化方程式を使用しているため、メモリオーバーヘッドはほとんどありません。使用事例に合わせて定数を自由に試してみてください)。
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秒ごとにカウンターを印刷します。泡立て、すすぎ、繰り返します。余分なクレジットが必要な場合は、ランニングカウンターを保持し、ランニングアベレージの合計秒数で割ります。
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;
}
}
});
どうやって!
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)」です。
(c ++のような)擬似コードでは、これら2つは、外部でトリガーされたカメラのセットから画像を処理しなければならなかった産業用画像処理アプリケーションで使用したものです。 「フレームレート」の変動にはさまざまな原因がありますが(ベルト上での生産が遅いか、速い)、問題は同じです。 (アプリケーションの開始または最後の呼び出し以降、msec(nsec?)のnrのようなものを与える単純なtimer.peek()呼び出しがあると仮定します)
解決策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
}
}
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
}