插帧式伪高帧率:用相机 VP 插值省 CPU,以及它带来的抖动 bug
插帧式伪高帧率:用相机 VP 插值省 CPU,以及它带来的抖动 bug
有一类"高帧率"不是真把游戏逻辑跑到高帧,而是渲染层插帧:游戏线程仍然稳定在 60 FPS tick,渲染层在两次真实帧之间插一帧出来 present,视觉上得到更高的刷新率。好处是 CPU 开销小(逻辑还是 60 FPS),代价是画质不如真高帧,而且限制多、容易出 bug。这篇记录它的实现原理和踩到的相机插值抖动问题。
原理:游戏 tick 不变,present 层插帧
游戏线程的 tick 频率不变(持续 60 FPS),高帧是"假"渲染出来的。每个 Vsync 周期里,除了正常的主绘制,还额外插一次绘制:
关键:插帧要插相机 view projection
如果插出来的帧和上一帧完全一样,那高帧就没有任何平滑感——等于白插。要让 present 更平滑,得对相机的 view projection 做插值。按插值率在当前帧和上一帧的 world view 之间求中间矩阵:
currentWorldView = interMatrix(worldView, prevWorldView, rate);
抖动 bug:只插相机 VP 会让运动物体抖
只对相机 view projection 插值,会导致正在移动的玩家角色抖动——这就是 bug 的根源。相机插值了,但角色本身的位置在插值帧里没有对应处理,两者错位,视觉上就是抖。
一开始怀疑是动画在高帧模式下插值抖动,于是加输出日志把每帧数据导到表里比对,数据验证下来排除了动画的嫌疑——不是动画引起的。真正原因是相机 VP 插值和物体运动不匹配。
解法:玩家和敌人用非插值 VP 渲染
对玩家角色,改用非插值的 view projection 来渲染(在插值绘制前先按真实 VP 捕获玩家),抖动就消失了。同理,Boss 和其他敌人也应该用非插值 view projection 渲染。也就是说:背景/环境吃插值 VP 得到平滑,动态角色走真实 VP 避免错位。
由此引出的深度 bug
上面这个"角色用非插值 VP、场景用插值 VP"的方案又埋了一批 bug:用非插值 view projection 计算深度,但渲染用的是插值 view projection,导致深度算错。表现为角色和场景的深度关系错乱(遮挡穿插)。这类 bug 全部同源——深度所用的 VP 和渲染所用的 VP 不一致。
优缺点权衡
- 优点:CPU 开销低——游戏线程始终 60 FPS tick,高帧是渲染层伪造的,逻辑侧不用加倍算。
- 缺点:
- 伪高帧的画面质量不如真正的高帧。
- 限制很多,且容易引入抖动、深度错乱这类 bug。
一句话:插帧式高帧是个"用画质和一堆 corner case 换 CPU"的折中方案,适合 CPU 吃紧又想要高刷观感的场景,但每加一类需要按真实 VP 处理的物体,就多一处要小心的深度/错位坑。