Canvas 奇巧淫技(三)性能优化和发光效果-上

4,427 阅读4分钟

这次主要写两个主题:

  • canvas 渲染的一些性能优化注意项
  • canvas 渲染发光效果的思路

最近忙里偷闲又更新了下 mapbox-plugins 这个库的一些功能和示例,其实很久没有加东西了,只是做点小 demo 爽一爽。主要是为了试验各种艺术效果,我们的目标是用代码搞点前端艺术,让数据能生动的展现。(有人质疑这种东西花里胡哨。。我就想说装饰下网站、博客,在可视化库中做轨迹动画,还可以扩展到其他应用场景。这种带来合理视觉体验的东西难道一点没价值是吗?!

canvas 性能优化几个点

1. 减少上下文切换!

这是第一点,我们都知道上班时候做多个任务,最费脑的就是上下文切换,从一个事情切换到另外一个复杂任务,可能先回顾之前写的代码就得十分钟。canvas 这类渲染机制也是,因为 renderingContext 的配置,大部分会涉及到 GPU 最终渲染时候的顶点渲染样式,片元栅格化样式(这点应该和WebGL类似吧)。改样式是很有较大性能开销的,特别是以下几个:

  • ctx.shadowBlur (blur类型的渲染尤其消耗性能
  • ctx.globalAlpha 频繁切换
  • ctx.strokeStyle, ctx.fillStyle频繁赋值(涉及到颜色字符串的解析!开销大!)
  • ctx.savectx.rotate 等坐标轴平移旋转的循环組合使用

比如这个多个alpha切换

比如之前我在做这个音频可视化库的时候,用到了许多全局透明度的修改,每个dot的颜色和透明度也许都不同,背景中也许还有方块的粒子发光效果。只能跑出 30fps,这其实不太理想。其中的原因就是 globalAlpha 在一次frame渲染中切换过多。而且为了渲染方便,在一次frame中有很多次以下坐标轴平移和旋转等操作。这样的渲染开销对CPU和GPU都造成比较大的负载。

// 针对每个方块粒子元素都做了这个操作, 这其实不太可取
ctx.save();
ctx.translate(dot.x, dot.y);
ctx.rotate(dot.rotate);
ctx.rect(0, 0, dot.size, dot.size);
ctx.restore();
ctx.fill();

可以看到图中 performance 显示,光是 ctx.globalAlpha 的切换消耗了接近10个毫秒,造成较低端的电脑上可能只有 20fps 以下。应该避免多次切换globalAlpha, 这个属性最多一次 frame 调用一两次, 足矣

解决方案,把渲染的这些压力,转换到CPU计算中去,例如刚坐标轴的反复平移旋转,这些是为了渲染方便(其实就是懒o(╥﹏╥)o 不想算几个三角函数),但是渲染恰恰是耗时的。最好是通过事先计算,来获取直接的渲染参数,直接调用最简单的rect,arc等指令去渲染。

2. 慎用 shadowBlur

这其中,shadowBlur 是性能杀手,虽然这个配置可以让我们的图形元素有外围一圈模糊,有点发光效果,但是有模糊效果的 CSS 样式或者renderingContext 都应该慎用。因为模糊算法中,一个像素的模糊,需要周边多像素的参与,比如写个高斯模糊函数,这是有额外性能开销的。

解决方案,在没有 shadowBlur 的场景下,会有效提高动画的帧率。发光效果应该用其他方式去实现,后面会讲到。

3. 避免不必要的渲染

最后这一点其实是很重要的,就是要跳出技术的角度,看看哪些元素是完全没必要渲染的,比如几万个圈。。画出来也没人看,看也看不清楚。在一个画面中渲染过多的图形元素有的时候是伪需求,不合理需求,应该砍杀

还有种技术解法是,在计算环节就事先把这一系列的数据进行像素插值,也就是映射到 canvas 的坐标系统中,映射到像素中去。

例如几百万个数据点,显示到屏幕上,并不意味着我们要去画几百万点。而是我们通过计算,得知哪些点会落在这个像素里,这个像素会呈现什么颜色即可。最终实际渲染的还是屏幕里有限的几百万像素 这样一来我们的渲染压力就小多了,这个思路在 Echarts 大数据渲染中也有用到,在流体场渲染中也经常用到。例如:github.com/cambecc/air 这里的源码中体现出了像素插值的思路

发光效果的思路

这个简单,留着明天写吧,太晚了,写不完了。。 除此之外,我还想记录下这个主题:

  • 空间索引库在要素选择和碰撞检测中的应用思路

最后分享下之前 fork 后增强的一个音频可视化库vudio,有一些瓶颈,正在去解决。希望這篇文章能给大家一些启发。

参考

Canvas 最佳实践(性能篇)

小程序Canvas优化实战

CSS 动画和性能加速