【性能篇】29 # 怎么给Canvas绘制加速?
创始人
2024-04-27 21:49:04
0

说明

【跟月影学可视化】学习笔记。

方法一:优化 Canvas 指令

例子:实现一些位置随机的多边形,并且不断刷新这些图形的形状和位置


优化 Canvas 指令

我们f12查看帧率,效果如下:Google Chrome浏览器怎么开启查看帧率功能?

在这里插入图片描述

对于一个 500 边形来说,它的顶点数量非常多,所以 Canvas 需要执行的绘图指令也会非常多,那绘制很多个 500 边形自然会造成性能问题。

下面减少绘制 500 边形的绘图指令的数量:用 -1 代替正 500 边形,如果type小于0表名多边形是正500边形,用 arc 指令来画圆


优化 Canvas 指令

优化完之后的效果:

在这里插入图片描述

方法二:使用缓存

具体做法就是将图形缓存下来,保存到离屏的 Canvas(offscreen Canvas)中,然后在绘制的时候作为图像来渲染,那就可以将绘制顶点的绘图指令变成直接通过 drawImage 指令来绘制图像,而且也不需要 fill() 方法来填充图形。

https://developer.mozilla.org/zh-CN/docs/Web/API/OffscreenCanvas

OffscreenCanvas 提供了一个可以脱离屏幕渲染的 canvas 对象。它在窗口环境和web worker环境均有效。


使用缓存

开启缓存效果:

在这里插入图片描述

缓存的局限性

  1. 如果需要创建大量的离屏 Canvas 对象,就会对内存消耗就非常大,有可能反而降低了性能。
  2. 缓存适用于图形状态本身不变的图形元素,如果是经常发生状态改变的图形元素,起不到减少绘图指令的作用。
  3. 不使用缓存直接绘制的是矢量图,而通过缓存 drawImage 绘制出的则是位图,所以缓存绘制的图形,在清晰度上可能不是很好。

方法三:分层渲染

简单点说就是用两个 Canvas 叠在一起,将不变的元素绘制在一个 Canvas 中,变化的元素绘制在另一个 Canvas 中。

满足两个条件

  • 一是有大量静态的图形元素不需要重新绘制
  • 二是动态和静态图形元素绘制顺序是固定的,先绘制完静态元素再绘制动态元素

在这里插入图片描述

上面就是两个canvas,一个动的,一个静态的,我们把它们叠在一起

在这里插入图片描述


分层渲染

方法四:局部重绘

如果元素都有可能运动,或者动态元素和静态元素的绘制顺序是交错的,可以使用局部重绘来处理,局部重绘就是不需要清空 Canvas 的全局区域,而是根据运动的元素的范围来清空部分区域。

canvas 提供 clip() ,能确定绘制的的裁剪区域,区域之外的图形不能绘制 CanvasRenderingContext2D.clip()

另外可以使用动态计算要重绘区域的技术,它也被称为脏区检测。它的基本原理是根据动态元素的包围盒,动态算出需要重绘的范围。

包围盒:指能包含多边形所有顶点,并且与坐标轴平行的最小矩形。

在这里插入图片描述

有兴趣的可以看看这篇:AntV Canvas 局部渲染总结

方法五:优化滤镜

用缓存优化版本的代码加上滤镜

在这里插入图片描述
可以看到直接干到 1.8 fps 了,说明滤镜对渲染性能的开销还是很大的
在这里插入图片描述

我们可以对 Canvas 应用一个全局的 blur 滤镜,把绘制的所有元素都变得模糊,没必要对每个元素应用滤镜,而是可以采用类似后期处理通道的做法,先将图形以不使用滤镜的方式绘制到一个离屏的 Canvas 上,然后直接将这个离屏 Canvas 以图片方式绘制到要显示的画布上,这样就能把大量滤镜绘制的过程缩减为对一张图片使用一次滤镜,下面调整一下代码:


优化滤镜

可以看到效果立竿见影:

在这里插入图片描述

方法六:多线程渲染

多线程渲染是用来优化非渲染的计算和交互方面导致的性能问题。比如渲染过程消耗了大量的时间,它可能会阻塞其他的操作,比如对事件的响应。这个时候可以利用浏览器支持 Canvas 可以在 WebWorker 中以单独的线程来渲染,这样就可以避免对主线程的阻塞,也不会影响用户交互行为。

具体的过程:

  1. 在浏览器主线程中创建 Worker
  2. 然后将 Canvas 对象通过 transferControlToOffscreen 转成离屏 Canvas 对象发送给 Worker 线程去处理

方法 HTMLCanvasElement.transferControlToOffscreen() 将控制转移到一个在主线程或者 web worker 的 OffscreenCanvas 对象上。

postMessage用法可以参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

我们新建一个 worker.js 文件,在里面写进之前优化好的代码,并且添加监听

// 创建正多边形,返回顶点
function regularShape(x, y, r, edges = 3) {const points = [];const delta = (2 * Math.PI) / edges;for (let i = 0; i < edges; i++) {const theta = i * delta;points.push([x + r * Math.sin(theta), y + r * Math.cos(theta)]);}return points;
}// 根据顶点绘制图形
function drawShape(context, points) {context.lineWidth = 2;context.beginPath();context.moveTo(...points[0]);for (let i = 1; i < points.length; i++) {context.lineTo(...points[i]);}context.closePath();context.stroke();context.fill();
}// 多边形类型,包括正三角形、正四边形、正五边形、正六边形和正100边形以及正500边形
// 用 -1 代替正 500 边形
const shapeTypes = [3, 4, 5, 6, 100, -1];
const COUNT = 1000;
const TAU = Math.PI * 2;// 创建缓存的函数
function createCache() {const ret = [];for (let i = 0; i < shapeTypes.length; i++) {const cacheCanvas = new OffscreenCanvas(20, 20);const type = shapeTypes[i];const context = cacheCanvas.getContext("2d");context.fillStyle = "red";context.strokeStyle = "black";if (type > 0) {const points = regularShape(10, 10, 10, type);drawShape(context, points);} else {context.beginPath();context.arc(10, 10, 10, 0, TAU);context.stroke();context.fill();}ret.push(cacheCanvas);}return ret;
}// 执行绘制
function draw(ctx, shapes) {const canvas = ctx.canvas;ctx.clearRect(0, 0, canvas.width, canvas.height);for (let i = 0; i < COUNT; i++) {const shape = shapes[Math.floor(Math.random() * shapeTypes.length)];const x = Math.random() * canvas.width;const y = Math.random() * canvas.height;ctx.drawImage(shape, x, y);}requestAnimationFrame(draw.bind(null, ctx, shapes));
}// 监听message
console.log('self------>', self)
self.addEventListener("message", (evt) => {console.log('message--->', evt)if (evt.data.type === "init") {const canvas = evt.data.canvas;if (canvas) {const ctx = canvas.getContext("2d");const shapes = createCache();draw(ctx, shapes);}}
});

在 html 文件里面引入该 js


多线程渲染

在这里插入图片描述

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...