游戏开发基础笔记:逻辑帧和物理帧辨析 | Gameloop | 游戏循环

显卡帧,物理帧(渲染帧),逻辑帧(轮)辨析

  • 首先显卡帧是指显卡接口输出的采样时间,这个理论采样时间无限短?但是由于物理信道线材的限制,传输是带通/低通信道,所以实际受限于接口和线材的带宽,所以才有 30帧 60 帧的限制,这个实现是通过采样 frame buffer 然后给绘制出来。而且由于并不能并行传输(指并行16亿bit)所以显示器必须用扫描的方法显示像素。这里我补充一下购买游戏显示器的时候,一般除了刷新率,还有一个响应时间的参数,这个响应时间基本就是从接口采样到冲刷到液晶面板(或者mini led+液晶、oled whatever)的时间,我们知道 60 帧也就 16ms 一帧,所以 16ms 的延时可能发生在任何地方(层次的思想,因为实际游戏从运算完成到显示出来是层层分包层层克扣的,看完下面会加深这一理解,实际到人眼再到人脑,也是有一个采样分包的,总之实际运输时间成本是不可忽略的,而且还相当大(相对于 CPU、GPU 1ns 能做的事情而言),就像一个西瓜产地买会很便宜),这也是为什么要有垂直同步技术(由于 CPU 可以通过协商时的硬件时钟知道显示器什么时候会冲刷,所以垂直同步是纯OS端软件实现的,而类似 Gsync、Freesync 等技术是为了减轻 OS 端 CPU 的负担,让这个同步过程让显示器的单片机做)来缓解这个 16ms 和实际渲染完成的 intersect 的 interval 不一致引发的撕裂问题,总之如果你加入了一些滤波比如护眼模式、动态模糊等显示器/TV 特效,这个响应时间是层层递增的。
  • 逻辑帧是每秒进行物体对象状态更新的计算次数,他决定了世界时间的运行推进。
  • 物理帧是显卡渲染 pipeline 进行渲染的帧数,基本最简单不影响游戏性能统计帧数的方式是通过一个变量记录 lastframebegin ,然后每次 render 开始的时候用 current time - lastframebegin 得出单帧渲染时间,然后用 1000 除以他,比如渲染一帧间隔了 33.33333 ms,那么就是 30 帧,如果隔了 16.6666 ms 就是 60 帧 etc。对于第三方显示帧数的方法应该是利用 GPU 的中断,这个可能要 O/S 提供 API 支持,因为渲染任务完成后,GPU硬件模块会产生中断告诉CPU,CPU响应中断后触发中断服务例程,这个如果提供了钩子,就能给上层获取数据一个方法了,之后再叠加渲染一个帧数数字就行了(额,可能要用比较底层的方法,写显存之类的?)。
  • 控制逻辑帧和渲染帧分离的方法,通过限制 gameloop 中 update 的时间间隔,如果没有达到时间 offset,直接等于什么都不做,然后这里的渲染也不一定是要 GPU 重新渲染一遍,如果根本就没有更新自然也可以不渲染

  • 限制,这种情况是理想的更新逻辑小于 offset 的,比如整个世界更新外加显卡的渲染都保证在 33 ms 内完成,就能保证有稳定的 30 帧。RoboCatRTS 的方案是 timer spin。如果没有计算上性能的延迟理论上 spin 是正确的,但是对于下面这种情况,说明 spin 是不能采用的:

这种情况是当计算或渲染超时的时候,下一轮 gameloop 开始的时候,更新了这次渲染的时间,这样显卡或者 CPU 突然的卡顿或者短暂处理复杂场景时情况来说整个世界时间推移对于所有终端来说就不是同步的了(而且他的延迟越来越大)。

  • 一种正确的方法是 spin update 来追赶时间,而渲染不是必要的,渲染只是游戏状态的一个 graphic 采样,重要的是保证世界的状态是和真的时钟同步的。

灰色点是 gameloop,红色点是触发了 update,空白(红字)是阻塞了,不管阻塞在哪。(但是一般的情况要么是渲染卡了要么是网络 io 卡,除非超大 RTS CPU 100% 一般都不会卡在计算上的)。

  • 这里的一个要点是 lag 是累计的,而不是上面那种丢弃的,只要不管什么延迟,只要回到了新的一轮 gameloop,而且 lag 有超过一逻辑帧的延迟就要不断地 update,而且就算是 update 他里面延迟了,比如网络包没收到,那也是延迟,下次进入 game loop 照样有 lag 累积。
  • 由于这里的 update 完了并不一定 lag 就清零了,这说明实际的逻辑时间应该发生在当前 update 完状态再过了一小会儿,所以需要 render 传入一个时间进行插值渲染,这个渲染实际是指之前games101里学的定制 rendering pipeline 的整个东西,包括各种物体的运动,光线等。
  • 无论是 spin 还是这种,都很消耗 CPU,如果要用 sleep 来实现较少的 update 对应较多的帧数,比如 16 个逻辑帧对应60个渲染帧,中间的状态让 GPU 插值渲染,这样就节省 CPU 的功耗了,这个需要用到系统的定时器支持,而 OS 就还要 spin,所以是无解的。一般要用硬件的计时器,不过这样别的更多问题了和更加多复杂度了我们有。不过实际这个和 granularity 有关,一个 context switch 大概会是几 us 到 几十 us,linux 除了 sleep 按秒计的还有 usleep 按微秒计的。(理论还有 nanosleep,但是没意思,一个 context switch 肯定几千ns)。
  • kernel 的计时器是这样的,对于芯片而言,分频器有正确的基频,硬件给操作系统提供一个时钟中断(在 CLINT 里,8086 是用 8253),这样能够更新系统的时间。对于 time sharing 系统,就必须做一个 mux,这个有点麻烦,只能用数据结构来实现!所有的超时回调和阻塞都和这个 mux 有关。mux 的基本实现的无非链表,队列,最小堆,其他高级一点的就是时间轮(Linux 用的就是多级时间轮实现的),红黑树(The high-resolution timer code uses an rbtree to organize outstanding timer requests.),至于为什么用红黑树而不是普通的 priority queue,有两个原因,一个是内存需要连续的,不然就要用数组指针来搞,然后 heap(pq)的删除是必须 O(n) 查找的,say that 某个注册回调不需要了或者某个进程需要 block 掉或者出异常 kill 掉就需要 remove 了,而红黑树全体有序,所以更胜一筹。而且如果都用红黑树,不用维护太多内核数据结构 adt。kernel 进行操作的延迟不会超过几十条指令,对 1Ghz CPU 来说,不过点 ns 而已,这样反而对 nanosleep 的精确度存疑了。
  • 但是 gameloop 如果要用 os 的定时器一般要用 windows 的,windows 的定时器分辨率太低了(普通 15ms,最高清也只有 1ms whereas 一帧 16.666ms in 60fps)基本用不了。不知道服务器演算的要不要用?也不用,因为 render 的bound(这里是因什么而受阻的意思) 本来就不在 server 的 gameloop 上,而客户端的 render 本来是独立于世界状态可以先行的(比如各种大招过场动画,中途放 CG,技能前摇,更不用说那些模拟类的如 fps,比如守望先锋等,一般的技能本身到生效就有 1s 内的延时,这一段时间内基本够硬件发挥了。如果网络卡爆了丢包延迟,像 PUBG 就直接掉线 T 出去了,显卡卡则没关系,只是你看到卡而已,充当别人的游戏体验吧。。。根据外挂猖狂的实现,推测应该是帧同步吧,搞外挂的应该知道是什么同步)。
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值