在游戏中每秒计算帧的一种好的算法是什么?我想将其显示为屏幕角落的数字。如果仅查看渲染最后一帧所花费的时间,则数字变化太快。
如果您的答案更新了每一帧并且当帧速率增加或减小时收敛不均,则奖励点。
您需要平滑的平均值,最简单的方法是获取当前答案(绘制最后一帧的时间)并将其与上一个答案合并。
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:哎呀。双忍者