Bir oyunda saniye başına kare hesaplama


110

Bir oyunda saniyedeki kare sayısını hesaplamak için iyi bir algoritma nedir? Bunu ekranın köşesinde bir sayı olarak göstermek istiyorum. Son kareyi oluşturmanın ne kadar sürdüğüne bakarsam, sayı çok hızlı değişiyor.

Cevabınız her kareyi güncellerse ve kare hızı artarken veya azalırken farklı şekilde birleşmezse bonus puan.

Yanıtlar:


100

Düzgün bir ortalamaya ihtiyacınız var, en kolay yol mevcut cevabı (son kareyi çizme zamanı) alıp önceki cevapla birleştirmektir.

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

0,9 / 0,1 oranını ayarlayarak 'zaman sabitini' değiştirebilirsiniz - bu, sayı değişikliklere ne kadar hızlı tepki verdiğidir. Eski cevabın lehine daha büyük bir kısım daha yavaş bir değişim sağlarken, yeni cevabın lehine büyük bir kısım daha hızlı değişen bir değer verir. Açıkçası iki faktör bire eklenmeli!


14
O halde, aptallık ve düzenlilik için, muhtemelen float weightRatio = 0.1 gibi bir şey istersiniz; and time = time * (1.0 - weightRatio) + last_frame * weightRatio
korona

2
Prensipte kulağa hoş ve basit geliyor, ancak gerçekte bu yaklaşımın yumuşatılması neredeyse hiç fark edilmiyor. İyi değil.
Petrucio

1
@Petrucio eğer yumuşatma çok düşükse, sadece zaman sabitini yükseltin (weightRatio = 0.05, 0.02, 0.01 ...)
John Dvorak

8
@Petrucio: önceki çerçevenin süresi anlamına last_framegelmez (veya en azından anlamına gelmemelidir ); timeson kare için hesapladığınız değeri ifade etmelidir . Bu şekilde, en son kareler en ağır şekilde ağırlıklandırılarak önceki tüm kareler dahil edilecektir.
j_random_hacker

1
Değişken adı "last_frame" yanıltıcıdır. "current_frame" daha açıklayıcı olacaktır. Örnekteki "zaman" değişkeninin global olması (yani tüm karelerde değerini koruması) ve ideal olarak bir kayan nokta sayısı olması gerektiğini bilmek de önemlidir. Ortalama / toplam değeri içerir ve her çerçevede güncellenir. Oran ne kadar yüksekse, "zaman" değişkenini bir değere oturtmak (veya ondan uzağa sürüklemek) o kadar uzun sürecektir.
jox

52

Bu, birçok oyunda kullandığım şey.

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

Bu yaklaşımı çok seviyorum. MAXSAMPLES'i 100 olarak ayarlamanızın belirli bir nedeni var mı?
Zolomon

1
MAXSAMPLES, fps için bir değer bulmak için ortalaması alınan değerlerin sayısıdır.
Cory Brüt

8
Ortalama (SMA) hareketli It sade
KindDragon

Mükemmel teşekkürler! Oyunumda ince ayar yaptım, böylece kene işlevi geçersiz olur ve başka bir işlev FPS'yi döndürür, ardından işleme kodunda FPS gösterilmese bile ana işlevi çalıştırabilirim.
TheJosh

2
Lütfen modulo kullanın, bir if değil. tickindex = (tickindex + 1) % MAXSAMPLES;
Felix K.

25

Tabii ki

frames / sec = 1 / (sec / frame)

Ancak, belirttiğiniz gibi, tek bir kareyi oluşturmak için geçen sürede çok fazla değişiklik var ve UI açısından kare hızında fps değerini güncellemek hiç kullanılamaz (sayı çok sabit olmadığı sürece).

İstediğiniz şey muhtemelen hareketli bir ortalama veya bir çeşit gruplama / sıfırlama sayacıdır.

Örneğin, son 30, 60, 100 veya sahip olduğunuz karelerin her biri için işleme sürelerini tutan bir kuyruk veri yapısını koruyabilirsiniz (hatta bunu tasarlayabilirsiniz, böylece sınır çalışma zamanında ayarlanabilir). İyi bir fps tahmini belirlemek için, kuyruktaki tüm oluşturma sürelerinden ortalama fps'yi belirleyebilirsiniz:

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

Yeni bir kareyi işlemeyi bitirdiğinizde, yeni bir oluşturma süresi sıraya alırsınız ve eski bir oluşturma süresini sıradan çıkarırsınız. Alternatif olarak, yalnızca oluşturma sürelerinin toplamı önceden ayarlanmış bir değeri aştığında (örn. 1 saniye) kuyruktan çıkartabilirsiniz. "Son fps değerini" ve son güncellenen zaman damgasını koruyabilirsiniz, böylece isterseniz fps rakamının ne zaman güncelleneceğini tetikleyebilirsiniz. Hareketli bir ortalamayla tutarlı biçimlendirmeye sahipseniz, her karede "anlık ortalama" fps yazdırmak muhtemelen uygun olacaktır.

Başka bir yöntem, sıfırlama sayacına sahip olmak olacaktır. Kesin (milisaniye) bir zaman damgası, bir kare sayacı ve bir fps değeri koruyun. Bir kareyi oluşturmayı bitirdiğinizde sayacı artırın. Sayaç önceden ayarlanmış bir limite (örneğin 100 kare) ulaştığında veya zaman damgası önceden ayarlanmış bir değeri geçtiğinden beri geçen süre (ör. 1 saniye) fps'yi hesaplayın:

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

Ardından sayacı 0'a sıfırlayın ve zaman damgasını geçerli saate ayarlayın.


12

Her ekran oluşturduğunuzda bir sayacı artırın ve kare hızını ölçmek istediğiniz bir zaman aralığı için bu sayacı temizleyin.

Yani. Her 3 saniyede bir, sayaç / 3'ü alın ve ardından sayacı temizleyin.


+1 Bu size yalnızca aralıklarla yeni bir değer verecek olsa da, bunun anlaşılması kolaydır ve ne diziler ne de tahmin değerleri gerektirmez ve bilimsel olarak doğrudur.
opatut

10

Bunu yapmanın en az iki yolu vardır:


Birincisi, burada benden önce diğerlerinin bahsettiği. Bence en basit ve tercih edilen yol bu. Sadece takip etmek için

  • cn: oluşturduğunuz karenin sayacı
  • time_start: saymaya başladığınızdan beri geçen süre
  • time_now: şimdiki zaman

Bu durumda fps'yi hesaplamak, şu formülü değerlendirmek kadar basittir:

  • FPS = cn / (time_now - zaman_başlangıç).

Sonra bir gün kullanmak isteyebileceğiniz harika bir yol var:

Diyelim ki dikkate almanız gereken 'i' çerçeveleriniz var. Şu gösterimi kullanacağım: f [0], f [1], ..., f [i-1], çerçeve 0, çerçeve 1, ..., çerçeve (i-1) oluşturmanın ne kadar sürdüğünü açıklamak için ) sırasıyla.

Example where i = 3

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

Ardından, i karelerinden sonra fps'nin matematiksel tanımı

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

Ve aynı formül, ancak yalnızca i-1 kareleri dikkate alındığında.

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

Şimdi buradaki hile, formül (1) 'in sağ tarafını, formül (2)' nin sağ tarafını içerecek ve sol tarafının yerine koyacak şekilde değiştirmektir.

Öyle (bir kağıda yazarsanız daha net görmelisiniz):

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)

Dolayısıyla bu formüle göre (matematiksel türetme becerim biraz paslanmış olsa da), önceki kareden fps'yi bilmeniz gereken yeni fps'yi, son kareyi oluşturmak için geçen süreyi ve kare sayısını hesaplamak için işlendi.


1
İkinci yöntem için +1. Bunun uber hassas hesaplama için iyi olacağını tahmin ediyorum: 3
zeboidlund

5

Bu çoğu insan için aşırı olabilir, bu yüzden onu uygularken yayınlamamıştım. Ancak çok sağlam ve esnektir.

Son kare süreleriyle bir Kuyruğu depolar, böylece ortalama bir FPS değerini, yalnızca son kareyi dikkate almaktan çok daha iyi bir şekilde doğru bir şekilde hesaplayabilir.

Ayrıca, o çerçevenin zamanını yapay olarak mahvedeceğini bildiğiniz bir şeyi yapıyorsanız, bir kareyi görmezden gelmenize de izin verir.

Ayrıca, çalışırken Kuyrukta saklanacak çerçeve sayısını değiştirmenize de olanak tanır, böylece sizin için en iyi değerin ne olduğunu anında test edebilirsiniz.

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

Burada iyi cevaplar. Onu nasıl uygulayacağınız, neye ihtiyacınız olduğuna bağlıdır. Yukarıdaki adam tarafından "zaman = zaman * 0.9 + last_frame * 0.1" koşu ortalamasını kendim tercih ederim.

ancak şahsen ortalamamı yeni verilere göre daha fazla ağırlıklandırmayı seviyorum çünkü bir oyunda ezmesi en zor olan ve bu yüzden beni en çok ilgilendiren SPIKES'tir. Bu yüzden, daha çok, bir .7 \ .3 bölme gibi bir şey kullanırım, ani artışların çok daha hızlı görünmesini sağlar (bunun etkisi de ekran dışına daha hızlı düşecektir .. aşağıya bakın)

Odak noktanız RENDERING zamanındaysa, o zaman .9.1 bölme oldukça iyi çalışır, çünkü daha düzgün olma eğilimindedir. Yine de oyun / yapay zeka / fizikteki ani artışlar, genellikle oyununuzun dalgalı görünmesine neden olacağından çok daha fazla endişe kaynağıdır (bu, 20 fps'nin altına düşmediğimizi varsayarsak genellikle düşük kare hızından daha kötüdür)

Yani, yapacağım şey şuna benzer bir şey eklemek:

#define ONE_OVER_FPS (1.0f/60.0f)
static float g_SpikeGuardBreakpoint = 3.0f * ONE_OVER_FPS;
if(time > g_SpikeGuardBreakpoint)
    DoInternalBreakpoint()

(Kabul edilemez bir artış olduğunu düşündüğünüz büyüklükte 3.0f'yi doldurun) Bu, FPS sorunlarını ortaya çıktıkları çerçevenin sonunda bulmanızı ve böylece çözmenizi sağlar .


time = time * 0.9 + last_frame * 0.1Ekranın sorunsuz bir şekilde değişmesini sağlayan ortalama hesaplamayı seviyorum .
Fabien Quatravaux

2

Çok sayıda eski kare hızı kullanmaktan çok daha iyi bir sistem, sadece şunun gibi bir şey yapmaktır:

new_fps = old_fps * 0.99 + new_fps * 0.01

Bu yöntem çok daha az bellek kullanır, çok daha az kod gerektirir ve ani kare hızı değişikliklerinin etkilerini düzleştirmeye devam ederken eski kare hızlarına göre son kare hızlarına daha fazla önem verir.


1

Bir sayacı tutabilir, her kare oluşturulduktan sonra artırabilir, ardından yeni bir saniyedeyken sayacı sıfırlayabilirsiniz (önceki değeri son saniyenin işlenen kare sayısı olarak saklayarak)


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

İşte Python kullanan eksiksiz bir örnek (ancak herhangi bir dile kolayca uyarlanabilir). Martin'in cevabındaki yumuşatma denklemini kullanıyor, bu yüzden neredeyse hiç bellek ek yükü yok ve benim için işe yarayan değerleri seçtim (kullanım durumunuza uyum sağlamak için sabitlerle oynamaktan çekinmeyin).

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

Sayacı sıfıra ayarlayın. Her çerçeve çizdiğinizde sayacı artırın. Her saniyeden sonra sayacı yazdırın. köpürtün, durulayın, tekrarlayın. Ekstra kredi istiyorsanız, çalışan bir sayaç tutun ve bir hareketli ortalama için toplam saniye sayısına bölün.


0

(C ++ benzeri) sözde kodda bu ikisi, harici olarak tetiklenen bir dizi kameradan görüntüleri işlemek zorunda olan endüstriyel görüntü işleme uygulamalarında kullandığım şeydir. "Kare hızındaki" farklılıklar farklı bir kaynağa sahipti (kayışta daha yavaş veya daha hızlı üretim) ancak sorun aynı. (Uygulama başlangıcından veya son çağrıdan itibaren size msn nr (nsec?) Gibi bir şey veren basit bir timer.peek () çağrınız olduğunu varsayıyorum)

Çözüm 1: Hızlı ama her kare güncellenmedi

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

Çözüm 2: Her çerçeve güncellendi, daha fazla bellek ve CPU gerektirir

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

Nasıl yaparım!

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

Kısacası, bir kene saati keneleri izler. İlk sefer ise, geçerli saati alır ve "tickstart" a koyar. İlk tıklamadan sonra, 'fps' değişkenini, tik saatinin kaç tikinin zaman eksi ilk tikenin zamanına bölünmesiyle eşit hale getirir.

Fps bir tamsayıdır, dolayısıyla "(int)".


1
Kimseye tavsiye etmem. Toplam işaret sayısının toplam saniye sayısına bölünmesi, FPS'nin matematiksel bir sınır gibi bir şeye yaklaşmasına neden olur, burada uzun bir süre sonra temelde 2-3 değere yerleşir ve hatalı sonuçlar görüntüler.
Kartik Chugh

0

İşte bunu nasıl yapıyorum (Java'da):

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'te, kare hızı ve kare süresi ortalamalarını hesaplamak için bu algoritmayı kullanıyorum:

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

kullanım:

statsUpdate();

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

İpucu: Örnekler 1 ise, sonuç gerçek zamanlı kare hızı ve kare süresidir.


0

Bu, KPexEA'nın cevabına dayanır ve Basit Hareketli Ortalama verir. Kolay kopyalama ve yapıştırma için derlenmiş ve TypeScript'e dönüştürülmüş:

Değişken beyanı:

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

İşlev:

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

Kullanım (uygulamanızda değişiklik gösterebilir):

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

-1

bir başlangıç ​​zamanı depolamak ve çerçeve sayacınızı döngü başına bir kez artırmak? birkaç saniyede bir framecount / (Şimdi - başlangıç ​​zamanı) yazdırabilir ve ardından bunları yeniden başlatabilirsiniz.

düzenleme: oops. çift ​​ninja

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.