帧率(FPS)基础:什么是帧率、帧时间、丢帧与卡顿
做性能优化这么多年,我经常看到新人一上来就问「FPS怎么提」。说实话,只盯着FPS数字看,容易走弯路。咱们先打下基础,把帧率、帧时间、丢帧、卡顿这几个概念理清楚。
帧率(FPS)—— 最熟悉的陌生人
帧率,全称 Frames Per Second,每秒能渲染多少帧。60 FPS 就是一秒60张画面。
我个人习惯把 FPS 当作一块「速度表」,但它的值其实是平均的。你想想看,游戏引擎每帧的耗时不可能完全一样,有的帧快(8ms),有的帧慢(20ms),平均下来 FPS 可能正好60。但用户感受到的流畅度,完全取决于最慢的那几帧。
帧时间(Frame Time)—— 真正的「底牌」
帧时间就是处理一帧需要的总耗时,单位通常是毫秒(ms)。
公式很简单:帧时间 = 1000 / FPS。反过来也一样:FPS = 1000 / 帧时间。
举个例子:目标60 FPS时,每帧的时间预算只有16.67ms。如果你的渲染、逻辑、物理等加起来超过了这个数,帧率就会往下掉。
| 目标 FPS | 目标帧时间(ms) | 我的建议 |
|---|---|---|
| 30 | 33.33 | 移动端低配保底 |
| 60 | 16.67 | 主流舒适区 |
| 90 | 11.11 | 高端体验线 |
| 120 | 8.33 | 电竞级 |
这里有个坑:很多人只盯着平均帧时间,忽略了**99%分位帧时间**。我在项目里常常看到平均帧时间只有10ms,但99%分位跳到30ms,那就说明每100帧里就有一帧卡成幻灯片。这种卡顿比稳定低帧更难排查。
丢帧(Frame Drop)与卡顿(Stutter)—— 一对孪生兄弟
丢帧就是某一帧因为超时而直接被跳过,或者渲染结果没有被显示。卡顿则是用户感知到的停顿、抖动。
说白了,丢帧是原因,卡顿是结果。一次丢帧可能用户察觉不到,但连续丢帧或者一帧耗时特别长,就会产生明显卡顿。
用代码看帧时间
拿 Unity 举例,你可以这样拿到每帧的时间:
float frameTime = Time.deltaTime * 1000f; // 单位ms
float fps = 1f / Time.unscaledDeltaTime;
if (frameTime > 16.67f)
{
Debug.LogWarning("Frame over budget! Frame time: " + frameTime);
}
Unreal 里就更直接了,控制台输入 stat unit 就能看到 Game、Render、GPU 分别的耗时。我习惯把这几行输出录到文件里,做长时间分析。
知识体系:帧率、帧时间、丢帧、卡顿的关系
下面的图可以帮你一次理清它们之间的关系。
从上到下看:帧率和帧时间互为倒数,你将实际帧时间与目标帧时间比较,一旦超时就标记丢帧,多次丢帧累积成用户感知的卡顿。明白这个链条,你就知道优化该从哪里入手了。
丢帧的两大类型
- 单帧尖峰 —— 某帧突然耗时极高(比如GC触发、资源加载),造成一次丢帧。这种丢帧频率不高的话影响较小。
- 持续高负载 —— 每一帧都超预算,帧率直接掉档(比如从60掉到30)。这种卡顿感受最明显。
我个人习惯用性能分析工具(如 Unity Profiler 或 Unreal Insights)记录每帧时间,然后看帧时间分布直方图。如果99%的帧都在16ms以下,那基本不会有卡顿。
小结几个关键点
- 不要只看 FPS,帧时间才是根本。
- 丢帧是帧时间超预算的直接后果,卡顿是丢帧的宏观表现。
- 优化目标应该是:降低平均帧时间 + 消除帧时间尖峰。
- 用工具抓取99%分位帧时间,比看平均值更靠谱。
嗯,这章的内容就到这。你把帧率、帧时间、丢帧、卡顿的关系吃透了,后面讲跨模块通信延迟的时候,就能更准地找到真正的瓶颈。咱们下章见。