记一次序列帧动画预渲染解决方案

7,280 阅读5分钟

背景

本文正在参加「金石计划 . 瓜分6万现金大奖」

主要在最近的免单突袭活动中,利用了大量的动画特效。 但是有一个 钥匙碎裂的动画特别卡, 一开始对问题的定义是不是页面上的序列帧动画太多,导致canvas 占用了 大量内存,因为播放这个序列帧动画的时候, 测试反应 安卓手机 卡的像幻灯片。但是苹果手机却没事。

原理

我们先看下 序列帧动画渲染的原理

  1. 第一步是将多张图片转成 一张雪碧图 这里其实已经确定了 动画的帧数
  1. 然后用 canvas 去渲染 每一帧

大家看下这张图:

显示 其实也就是 我们canvas 的容器, 而上面的 1-6 其实就是一张图, 所以序列帧动画的 原理 就是 将1-6 这张图 通过canvas drawImage api 每一帧 进行 截取,然后进行渲染。

表面看是没啥问题, 很简单的逻辑渲染,但是重新绘制的过程,实质上是一个不断刮白-重画的过程。但在屏幕上完成这一系列操作是需要一定时间的,而且屏幕上的图形越复杂,所花的时间就越长,我们肉眼可见的刮白-重画操作,在使用过程中就会让就会直接感觉到屏幕的闪烁。 这就 为啥 在 钥匙爆裂那几张动画卡顿。

为了验证我的猜想, 我将素材中的整体图片使用2x 图 去渲染, 在问题机型表现的卡顿几乎都是没有了,但是 随之带来的一个问题就是图片的质量 就不是很高。 这种对于开发而言, 就是要设计妥协,说图片太大。但是这种对于用户体验来说,就不太好。 所以就有了目前调研的预渲染 canvas , 其实 就是在渲染之前 将这些 canvas 渲染好

双缓存画布

现在我们有一幅图需要放在Canvas中,使用drawImage()方法,有三种写法:

// 将image放到目标canvas指定位置
void ctx.drawImage(image, dx, dy); 

// 将image放到目标canvas指定位置,指定宽高渲染
void ctx.drawImage(image, dx, dy, dWidth, dHeight);

// 将image裁剪之后放到目标canvas指定位置,指定宽高渲染
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

第一种方法只是把图片原样放到Canvas中,第二种方法指定宽高就意味着放大或者缩小图片后再放进去,第三种是将图片裁剪后再放大或者缩小放到canvas中,这三种写法操复杂度作依次增加,性能开销也随之增大。

而如果使用离屏渲染(即我们所说的双缓存画布),我们可以预先把图片裁剪成想要的尺寸,然后将该内容保存起来,绘制的时候直接使用第一种写法直接将图片放入Canvas中。

// 在离屏 canvas 上绘制var offscreencanvas = document.createElement('canvas');// 宽高赋值为想要的图片尺寸

offscreencanvas.width = dWidth;

offscreencanvas.height = dHeight;// 裁剪

offscreencanvas.getContext('2d').drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);// 在视图canvas中绘制

viewcontext.drawImage(canvas, x, y);

双缓存画布技术的核心在于系统需要在内存中开辟一块与当前画面等大的“逻辑屏幕“。我们的画图和动画操作都会先作用于这块”逻辑屏幕“中,当一个操作在这块”逻辑屏幕“上完成之后,再把整块”逻辑屏幕“投放到我们的屏幕上。

测试

我们先看下没有使用预渲染的 canvas 3x 图

before (1).gif

很明显在爆裂的这几幁动画,会卡顿, 好像就是卡住了 一样。

使用了 预渲染之后我们看下 播放的视频

after (1).gif

背后的 原理 其实很简单 看下这张图:

我们提前做好所有序列帧的在目标canvas 的切割, 在这样的过程之下,我们是无法看到整个图形在屏幕上的重绘过程,从而解决了闪烁问题。就好像看动漫一样,不用双缓存技术,就是画一帧看一帧,肯定会卡顿。而用了双缓存技术,会事先把每一帧画好,不断翻动展示出来。这里大家可以联想到 react 的双缓存 fiber 树哦, 就能理解了。

存在的问题

预渲染canvas 存在的问题有下面两个

  1. 第一个是 在 ios 端 对canvas 的内存 有限制,如果序列帧的动画过多, 导致canvas 拿不到上下文
  2. 第二个就是 创建canvas 的js 执行时间 比较久, 在 安卓低端机 表现十分明显
  3. 关于问题的改进策略 下一篇文章再去讲,留点悬念, 可以关注一波

未来展望

Webgl 渲染

这不一定是一种最好的方案,但是对于不借助 webgl 能力是一种优化手段, 如果后面还是对序列帧动画进行升级, 就是 基于webgl 去渲染每一帧的图片 ,这里可以参考pixi 渲染精灵图, 或者图集

pixijs.huashengweilai.com/guide/start…

对于雪碧图的 图片存储, 如果高效的渲染 。。。 我觉都是后面可以优化的方向

资源的预加载方案

对于资源超过几M 的图片方案, 如果不加loading页, 我们该如何让用户无感知,体验我们的游戏。 这些都是我们可以考虑升级的。