大家好,我是前端西瓜哥。在使用Canvas作为图形编辑器时,我们需要维护自己的图形树来保存图形的信息,定义元素之间的关系。我们改变画布中的某个图形来更新画布,最简单的方法就是清空画布,然后根据图形树重新绘制所有图形,在图形较少的情况下是没有问题的。但是如果图形很多,画的时候可能会卡住。那么,有什么办法可以优化吗?是的,脏矩形渲染。画布应该如何更新?这里我们假设一个场景,在画布上的随机位置绘制了大量的绿球,然后在最上面一层绘制了一个红球。现在我们想让红球随着光标移动,下面的绿球保持不变。我们如何更新它?首先我们先不考虑Canvas的分层方式,因为我这里为了方便使用了一个比较简单的场景。实际场景会比较复杂,通常是用光标选中一个元素并拖动,这就涉及到图形拾取的实现,元素会在任意层级。这里为了重点更新,去掉了这些不相关的点。OK,回到正题,想想怎么更新?一个容易想到的解决方案是更新整个体积,当鼠标移动时重新绘制所有的球。前面说了,当球数少的时候这不是问题,但是如果图形数逐渐增加,达到一定数量,就会出现GPU瓶颈,出现丢帧现象。因为很多图形是在很短的时间内画出来的。另一种解决方法是本文主题用脏矩形渲染,本质上是局部重绘。在讲解脏矩形渲染原理之前,我们先了解几个概念。脏矩形:改变图形的物理信息后,需要重新渲染的矩形区域通常是目标图形的当前帧和下一帧组成的边界框。边界框:包围图形的最小矩形。通常用作低成本的碰撞检测。因为矩形的碰撞检测算法简单高效,而复杂图形的碰撞检测复杂低效。脏矩形渲染简单来说就是计算两帧变化的目标图形生成的边界框(脏矩形),清空该区域,然后重绘该区域内所有与脏矩形相交的图形。对于红球移动的场景,具体逻辑是:计算当前帧和下一帧红球形成的边界框,这个边界框就是脏矩形。遍历绿球的物理信息,计算它们的边界框,取出与脏矩形相交的绿球。清空脏矩形区域。将dirtyrectangle设置为裁剪区域,这样就只能在dirtyrectangle中绘制。绿球按顺序抽,红球最后抽。顺序是为了保证层级是正确的。与全图相比,部分绘制可以有效减少需要绘制的图形数量,减少对GPU绘图指令的调用,从而提高渲染性能。这里还有一个优化点,就是减少遍历图形的数量,可以通过使用四叉树碰撞检测来优化。具体读者可以自行上网搜索,稍后我会写一篇文章进行说明。脏矩形渲染的具体实现可以看这个在线demo:https://codesandbox.io/s/1jr5lj。其中有如下一段代码,可以通过注释和反注释来选择“全局渲染”或者“脏矩形渲染”。canvas.addEventListener("mousemove",(e)=>{constx=e.clientX;consty=e.clientY;//重新渲染所有(性能不佳)//ctx.clearRect(0,0,canvasWidth,canvasHeight);//drawGreenBalls(greenBalls);//drawRedBall(x,y);//部分重新渲染(良好的性能)partRender(x,y);});另外,可以通过greenBallCount变量设置绿球的数量,测试性能上限。然后说说涉及到的一些简单的算法,可以在我的github项目中找到:https://github.com/F-star/graphics-algorithm。TypeScript类型:导出接口IPoint{x:number;y:数字;}导出接口IRect{x:数字;y:数字;宽度:数字;height:number;}/***IRect数组,数组长度必须大于等于1*/exporttypeINoEmptyArray
