2D图形渲染中的脏矩形

脏矩形 渲染

1.白鹭引擎的脏矩形
白鹭引擎提供了脏矩形功能。白鹭引擎的脏矩形只是对整个显示舞台设定一个矩形区域,而不需要考虑具体的显示对象的容器及嵌套关系, 白鹭脏矩形主要提供了两个方法:

egret.RenderFilter.getInstance().addDrawArea( egret.Rectangle );
egret.RenderFilter.getInstance().clearDrawArea();

白鹭引擎脏矩形基本用法如下:

//先重置脏矩形

egret.RenderFilter.getInstance().clearDrawArea();

/*在项目中我们使用addDrawArea的地方,在前面加一句重置。
防止出现当前场景设置脏矩形跟前一个场景设置的脏矩形叠加的问题,
这样就可能对脏矩形功能提升性能打折扣。*/

var rect = new egret.Rectangle( 0, 0, stageW, stageH - 200 ); 
egret.RenderFilter.getInstance().addDrawArea( egret.Rectangle );

白鹭引擎场景管理的标准做法是,每个使用脏矩形功能的场景结束的时刻,都应该重置一下脏矩形。

2.canvas脏矩形渲染
在annie2x的2D图形渲染中,显示列表是非常成熟的一种渲染组织方式,它封装了晦涩难以操作的底层绘图接口,提供给开发者一套非常简易且高效的API,来组织显示元素最终呈现到屏幕上。

全屏刷新渲染

基于显示列表结构的渲染方式,最常的就是全屏刷新模式。这也是目前大多数游戏引擎普遍采用的方案,因为不管是原理还是实现都比较简单:首先设定一个时钟频率,例如通常是每秒执行60次。每次都单独刷新一次屏幕。刷新过程就是直接清空整个屏幕,然后从显示列表的根节点开始遍历,按顺序找到每个可呈现的显示对象节点,按照它的坐标和大小绘制到屏幕上。这样开发者只需要改变显示对象的位置属性,等待下一次时钟周期到来,改变就会自动刷新到屏幕上。由于通常情况下,并不是每秒60次显示列表每次都会发生改变,或者发生改变时仅有一小部分改变。因此清空整个屏幕重绘的方式虽然实现简单,但是不必要的开销比较大。

脏矩形渲染

脏矩形渲染是一种基于显示列表的局部刷新方法。依然是要有一个时钟频率,定时每秒执行60次。但区别是每次我们并不直接清空整个屏幕,而是首先计算屏幕上发生改变的区域,这里我们叫做重绘区,然后只清空指定的重绘区,并找出跟这个区域相交的所有显示对象重绘一遍。如果显示列表本次美并没有发生改变,那么将直接跳过本次绘制,什么也不做。

显而易见的是它能大幅提高屏幕整体渲染性能,特别是对于复杂UI界面的情况,全屏刷新算法会每秒60次不停地刷新所有UI对象,在有脏矩形渲染的情况下,哪改变绘制哪,极端情况直接跳过绘制,在复杂UI界面的情况非常容易达到满帧。另外脏矩形渲染能够节省设备电量以及降低发热量。我们曾经测试过同一个线上游戏,在更新到脏矩形渲染后,整体耗电量降到了原先的30%,发热量也从45度降低到了35度,结果还是非常显著的。

如何局部刷新

这里简单说一下实现局部刷新的底层接口。如果使用的是Canvas2DRenderingContex,比较简单:

context.save();
context.clearRect(region.minX, region.minY, region.width, region.height);
context.rect(region.minX, region.minY, region.width, region.height);
context.clip();
//在此绘制相关的显示对象...
context.restore();

开始前先save()保存上下文环境,然后用clearRect()清空目标区域像素,然后用rect绘制一个相同的矩形路径(可以自行多次绘制,区域会叠加),接着clip()一下,之后所有的绘制操作都会自动被限制在rect的区域内。只要按正常方式去把跟重绘区相交的对象都绘制一遍就好了。最后是调用restore()取消clip()的限制。

如果使用的WebGL或OpenGL,步骤略复杂一些,原理是开启Stencil模板缓冲,模拟clip()方法即可,mask遮罩功能也是用相同的方式实现的。这里不再赘述。

获取重绘区

接下来,局部刷新算法的重点就集中在了如何获取重绘区上。

显示对象在屏幕上的矩形区域

如上图,每个显示对象在屏幕上都对应一个矩形区域。通常它自身都包含两个属性:自身矩阵和自身矩形。自身矩阵代表这个显示对象在父级容器中的位置,缩放等变换信息,也就是开发者最通常操作的部分。自身矩形是固定测量出来的一个四边形,如果是一个图片,自身矩形等于这个图片未缩放时的原始大小区域。自身矩形和自身矩阵叠加,会得到这个显示对象在它父级容器里变换后(例如位移或缩放)的矩形区域。但是为了能统一比较,我们需要获取每个显示对象在屏幕上的矩形。那么首先是要获得这个显示对象相对于屏幕的矩阵信息,屏幕矩阵等于自身矩阵和所有父级容器的矩阵相乘结果。最后用屏幕矩阵跟自身矩形叠加就能得到屏幕矩形。

获取到每个显示对象在屏幕上的矩形区域后,我们下一步是需要一个通知机制,每当这个显示的对象的屏幕矩阵和自身矩形发生改变的时候,就重新计算一次屏幕矩形。这时候就能得到两个矩形:这个显示对象改变前在屏幕上的矩形,和改变后在屏幕上的矩形。这两个矩形就是要获取的重绘区。

获取重绘区域

如上图,一张图片从屏幕的左边移动到了右边,那么这个操作会产生左右两个需要重绘的区域。将两个区域都清空,并根据图片新的位置绘制到新区域上,这样图片就从旧位置移动到了新位置。

合并重绘区

上图是单个显示对象移动的情况,缩放的情况也可以此类推。但实际情况中,每次刷新时并不只有一个显示对象改变,可能会产生无数个可能相交的重绘区域。最简单的范式是直接把这大量的矩形依次拿去重绘,分别执行清空矩形,然后查找相交的对象重绘。显然重绘区数量过大的时候,会造成巨大的开销。因此我们还需要对得到的重绘区列表进行合并。这里就进入到了脏矩形算法的核心了,合并的算法规则是:

  • 如果两个矩形合并后总面积小于两个矩形各自面积之和,则允许合并,并且优先合并相交面积最大的两个矩形。

  • 合并到最后,若规则1已经不满足,但是剩余矩形总数量大于3,则强制继续合并。并优先合并面积增加量最小的两个矩形。

剩余矩形总数为什么控制为3以内?这其实是一个测试结论,把合并到最后重绘区数量控制为三个以内的时候是最佳的平衡点。既不会对接下来的重绘操作带来压力,也足够覆盖大部分的局部刷新情况。

合并重绘区域

如上图,屏幕上有大量的重绘区域,我们根据刚刚的合并原则, 能够得出最终的三个重绘区。注意一下左下角的重绘区,实际上内部两个矩形并没有相交。也就是说他们合并后的矩形肯定大于各自的面积之和。这里正是使用了规则2将其合并的。再极端一些,整个屏幕布满了零碎的矩形都不相交,那么这个算法结果会得到唯一的一个全屏矩形。这样正好符合全屏刷新的需求,能让这个合并算法同时适应局部刷新和全屏刷新的情况。

来源:https://idom.me/articles/841.html



  • 发表于 2018-08-17 12:59
  • 阅读 ( 1691 )
  • 分类:Html5前端

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
不写代码的码农
大北兔

15 篇文章

作家榜 »

  1. 大北兔 15 文章
  2. 皮卡丘先生 13 文章
  3. hero 10 文章
  4. vien007 7 文章
  5. ningbnii 4 文章
  6. Even 4 文章
  7. 炸天 4 文章
  8. anlun214 4 文章