AntV 教你 2D Canvas 渲染优化

作者 : 开心源码 本文共3706个字,预计阅读时间需要10分钟 发布时间: 2022-05-13 共278人阅读

简介

HTML 上的图形渲染主要有两种方案 SVG 和 Canvas,前者更易于使用,然后者潜力更大,本文主要关注如何使用 Canvas 绘制出更多的图形,提供更加流畅的交互。本文的内容有:

  • 渲染机制
  • 性能瓶颈
  • 绘制更多的图形
  • 让交互更流畅
  • webGL 实现 2D 渲染

渲染机制

我们以简单的一个圆为示例,来比照 SVG 和 Canvas 的渲染:<br /> image.pngimage.png

  • SVG 同其余的 HTML 标签一样,每个图形对应一个标签,图形的绘制同 HTML 标签一致
<svg><circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red"/></svg>
  • 而 Canvas 本质上是一个图片,无论有多少个图形只有一个标签,需要使用 javascript 来绘制图形
<canvas></canvas><script>  var ctx=c.getContext("2d");    ctx.strokeStyle = 'black';    ctx.fillStyle = 'red'    ctx.lineWidth = 2;  ctx.beginPath();  ctx.arc(100,50,40,0,2 * Math.PI);  ctx.stroke();</script>

PS:你可以把 SVG 的了解成制作完一个个的图形放到页面上,而 Canvas 则是使用画笔一个个的绘制图形。<br />这篇文章并不是比照 SVG 和 Canvas 差异的文章,两者的差别 w3cshool 的形容非常精确。

如何拾取图形

circle-hit.gifcircle-hit.gif

<br />因为 SVG 的图形是一个个的 HTML 标签,所以 SVG 的图形天然支持浏览器的所有事件。而 Canvas 是一张画布,使用一支笔将图形绘制到画布上后,这些图形就成为了画布的一部分,每个图形都无法独立的对浏览器的事件进行相应。有什么方式判断指定点所在的图形呢?主要有两种方案:

  1. 浏览器提供了 isPointInPath 和 isPointInStroke 两个方法判定点能否在图形内,点能否在图形的边上。
  2. 缓存所有图形的属性,使用数学方法来判断指定点所在的图形。

我们仍然以一个圆为示例,来看这两种方式:

浏览器的方法

使用浏览器的方法需要重新绘制一遍图形:

 ctx.beginPath(); ctx.arc(100,50,40,0,2 * Math.PI); const inPath = ctx.isPointInPath(100, 100); const inStroke = ctx.isPointInStroke(100, 100);
  • 这是一种使用简单,同时所有图形都适用的方式,但是成本巨大,例如:鼠标每在画布上移动一次,都会导致所有的图形绘制一遍。
  • 建议图形个数小于 500 个时使用这个方案。

数学拾取

需要对每种图形提供判断能否在图形内部和图形边上的方法:

function isInCircle(point, x, y, r) {    return distance(point.x, point.y, x, y) <= r;}function isInCircleStroke(point, x, y, r, lineWidth) {  const d = distance(point.x, point.y, x, y);    return d <= r + lineWidth / 2 && d >= r - lineWidth / 2;}const point = {x: 100, y: 100};const inPath = isInCircle(point, 100, 50, 40);const inStroke = isInCircle(point, 100, 50, 40, 2);
  • 性能好:从性能上来说数学拾取的性能比使用浏览器的方法要快 20 倍左右
  • 实现复杂:从实现上来说需要实现所有几何图形的数学计算,更多的数学计算参考 2D 图形计算

PS:两者的性能测试比照

浏览器 API数学计算
1111.029999970924114.779999959282577
2110.530000005383045.694999999832362
3117.555000004358597.979999994859099
4126.25999998999765.354999972041696
5110.89499999070544.725000006146729
6121.65499996626756.2049999833106995
7121.185000054538254.529999976512045
8116.785000020172458.094999997410923
9124.060000001918528.925000031013042
10124.424999987240884.849999968428165
平均值118.437999999150636.113999988883734

更多更快的拾取方案可以参考 2D 图形拾取方案

图形的升级

对于 SVG 的图形来说,直接修改对应标签的属性就可,有浏览器控制刷新图形。但是对于 Canvas 来说需要清理整个画布,重新绘制所有的图形,也就是说 Canvas 画布上有 10W 个图形,仅仅升级一个图形时,其余 99999 个图形也需要重新绘制。

function drawAll() {     // 绘制所有图形}function repaint() {    ctx.clearRect(0, 0, width, height);  drawAll();}

性能瓶颈

从上面的渲染机制我们可以自然的推导出 Canvas 的图形渲染的性能瓶颈主要在三方面:

  • 同一时间绘制过多的图形,会阻塞浏览器的进程,导致页面不能响应
  • 鼠标在画布上移动时,假如不能及时捕捉鼠标,会导致卡慢
  • 图形升级时,重绘的时间过长,则帧率非常低

渲染的成本

我们以绘制 1W 个圆作为示例,来看一下单次绘制的成本:image.pngimage.png

需要 51 ms,如果我们要绘制 10W 个点,则需要 510ms

拾取的成本

前面我们测试过图形拾取和数学拾取的差异,1W 个圆的拾取需要 11ms左右,假如再加上图形刷新的响应,可以预期帧率会非常低。

升级的成本

我们以鼠标在画布上移动,移入一个圆这个圆变颜色,我们来看一下画布整体刷新时的效果:<br />circle-hit1.gifcircle-hit1.gif

  • 可以看到显著的推迟,鼠标移动开一段距离后,点才响应
  • 鼠标移动过的路径,大部分圆没有响应

假如我们对圆进行动画看一下帧率:<br />circle-hit2.gifcircle-hit2.gif

<br />可以看到动画的帧率在 8 帧左右

绘制更多的图形

初次渲染时的优化

当一次渲染的图形过多时,将一次渲染分成屡次渲染,每次渲染时间添加几毫秒的间隔,这时候就不会卡慢:image.pngimage.png

<br />这种方案尽管会添加总的渲染时长,但是可以降低页面的卡慢感,对所有图形进行整体升级时也可以使用这个方案,但是进行交互时这种方案会带来肯定的推迟。

draw2.gifdraw2.gif

  • 这是一个 10W 个点渲染(局部 画布 1000 * 1000,这里仅显示了500* 500 的范围)的效果,可以保证近乎 60帧的效果
  • 分段渲染的核心在于中间空白的间隔要足够小,这里面有很多的算法,就不在这里开展
  • 分段渲染时数据升级时怎样解决,可以参考 react fiber 的实现。

频繁渲染的优化

鼠标在画布上移动时,不断的导致重绘,我们只需能够保证 60 帧的重绘频率就可,所以重绘的间隔不能小于 16ms,我们可以将持续渲染的同步机制,改成每 16 ms 渲染的异步推迟渲染机制,这样可以大大降低重绘的频率。

image.pngimage.png<br />异步渲染仍然有很多需要思考的地方,可以参考推迟渲染实现

升级的优化

我们可以实现图形的局部刷新,在局部刷新时仅清空图形所在的包围盒,所有与这个包围盒相交的图形一律刷新,这时候我们来看上面的两个示例:当鼠标在画布上移动时,这就流畅多了,基本上没有推迟

circle-hit4.gifcircle-hit4.gif同样的动画,可以看到支持了局部刷新后可以直接到达 60 帧<br />circle-hit3.gifcircle-hit3.gif局部刷新的具体实现比这复杂的多,后面我会写一篇更详细的关于局部刷新的文章,这里可以参考 局部刷新文档

拾取的优化

拾取的优化我们在上面已经进行了简单的说明,数学拾取的性能远远超过使用浏览器的方法来拾取,更多的拾取方案参考:2D 图形拾取方案

webGL 实现 2D 渲染

渲染性能的提升

因为 webGL 的渲染是在 GPU 中进行,可以明显的提升渲染效率,可以看下面的示例:

draw3.gifdraw3.gif

  • 这是一个 80 * 80 * 80 = 512000 个点的示例

少量限制

  • 因为 webGL 的渲染时基于光栅(点),所以绘制线时本质上是通过一个个的点来绘制,逐点计算贝塞尔等曲线不现实,因而绘制的这些曲线不够平滑。
  • 绘制图形时尽量不要直接在 cpu 中计算各个图形的几何模型,而使用 shader 对图形进行计算和渲染,否则性能反而会下降
  • 文本的渲染非常复杂,也不会带来性能的提升

总结

这些优化大部分已经在 2D 绘图引擎 G上实现 ,2D 图形的渲染优化主要在异步渲染、拾取加速和局部渲染三个方面,但是每个方面都非常复杂,都可以独立成章,本文仅仅是从思路上进行讲解,更多更细的分析都会在后面提供独立的章节进行讲解,敬请期待!

AntV 官网:https://antv.vision/
2D 绘图引擎 G: antvis/g

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » AntV 教你 2D Canvas 渲染优化

发表回复