我正在开发一个基于 HTML5 的游戏,我想在其中混合在网格中数百个方形单元格上绘制的不同颜色的半圆形区域。效果类似于热图。经过一番研究,我发现了上面由 Simon Sarris 记录的“阴影”技术。
实施这项技术提供了我想要的外观。我喜欢它很容易理解。然而,在实践中,我发现与我之前绘制数千个填充矩形的技术(但没有吸引力)相比,即使渲染几个(~150)阴影也要慢得多。
所以我决定做一些分析。我编写了一些基本的 JavaScript(其修改版本可以在https://jsfiddle.net/Flatfingers/4vd22rgg/看到)将五种不同形状类型中的每一种绘制 2000 个副本到 1250x600 画布的非重叠部分,记录在五个主要桌面浏览器和移动 Safari 的最新版本中,这五个操作中的每一个的经过时间。(抱歉,桌面 Safari。我也没有手边的 Android 来测试。)然后我尝试了不同的效果组合并记录了经过的时间。
这是我如何绘制两个渐变的简化示例,其外观类似于阴影填充弧:
var gradient1 = context.createRadialGradient(75,100,2,75,100,80);
gradient1.addColorStop(0,"yellow");
gradient1.addColorStop(1,"black");
var gradient2 = context.createRadialGradient(125,100,2,125,100,80);
gradient2.addColorStop(0,"blue");
gradient2.addColorStop(1,"black");
context.beginPath();
context.globalCompositeOperation = "lighter";
context.globalAlpha = 0.5;
context.fillStyle = gradient1;
context.fillRect(0,0,200,200);
context.fillStyle = gradient2;
context.fillRect(0,0,200,200);
context.globalAlpha = 1.0;
context.closePath();
时间安排
(2000 个不重叠的形状,设置 globalAlpha,drawImage() 用于渐变,但不用于阴影)
IE 11 (64-bit Windows 10)
Rects = 4 ms
Arcs = 35 ms
Gradients = 57 ms
Images = 8 ms
Shadows = 160 ms
Edge (64-bit Windows 10)
Rects = 3 ms
Arcs = 47 ms
Gradients = 52 ms
Images = 7 ms
Shadows = 171 ms
Chrome 48 (64-bit Windows 10)
Rects = 4 ms
Arcs = 10 ms
Gradients = 8 ms
Images = 8 ms
Shadows = 203 ms
Firefox 44 (64-bit Windows 10)
Rects = 4 ms
Arcs = 21 ms
Gradients = 7 ms
Images = 8 ms
Shadows = 468 ms
Opera 34 (64-bit Windows 10)
Rects = 4 ms
Arcs = 9 ms
Gradients = 8 ms
Images = 8 ms
Shadows = 202 ms
Mobile Safari (iPhone5, iOS 9)
Rects = 12 ms
Arcs = 31 ms
Gradients = 67 ms
Images = 82 ms
Shadows = 32 ms
观察结果
- 在填充形状中,填充矩形始终是所有浏览器和测试环境中最快的操作。
- 在 IE 11 和 Edge 中,填充的完整圆弧(圆形)比填充的矩形慢约 10 倍,而在其他主要浏览器中则慢约 3.5 倍。
- 在 IE 11、Chrome 48 和 Opera 34 中,渐变大约比矩形慢 3 倍,但在 Firefox 44 中慢了 100 倍(参见Bugzilla 报告 728453)。
- 在所有桌面浏览器中,通过 drawImage() 生成的图像大约是填充矩形的 1.5 倍。
- 阴影填充弧是最慢的,从 IE、Edge、Chrome 和 Opera 中的填充矩形慢约 50 倍到 Firefox 中的 100 倍慢。
- Chrome 48 和 Opera 34 在除阴影填充弧外的所有形状类别中都非常快速,但它们并不比那里的其他浏览器差。
- 当 drawImage() 绘制 1000 个形状时,Chrome 和 Opera 崩溃,其中 shadowOffsetX 或 shadowOffsetY 的值超出了物理屏幕分辨率。
- IE 11 和 Edge 绘制弧线和渐变的速度比其他桌面浏览器慢。
- drawImage() 在移动 Safari 上运行缓慢。实际上,绘制多个渐变和阴影弧线比使用 drawImage() 绘制一个副本多次要快。
- 在 Firefox 中绘图对先前的操作很敏感:绘制阴影和渐变会使绘制弧线变慢。显示最快的时间。
- Mobile Safari 中的绘图对先前的操作很敏感:阴影使渐变变慢;渐变和弧线使 drawImage() 甚至比通常更慢。显示最快的时间。
分析
虽然 shadowOffset 功能是一种简单且视觉上有效的混合形状的方法,但它比所有其他技术慢得多。这限制了它对只需要绘制少量阴影并且不需要快速重复绘制很多阴影的应用程序的有用性。此外,当使用 drawImage() 加速时,给 shadowOffsetX 或 shadowOffsetY 一个大于大约 3000 的值会导致 Chrome 48 和 Opera 34 挂起近一分钟,消耗 CPU 周期,然后使我的 nVidia 显示驱动程序崩溃,即使在更新它之后也是如此到最新版本。(谷歌搜索没有发现 Chromium 的错误报告描述了当一个大的 shadowOffset 和 drawImage() 一起使用时这个错误。)
对于需要混合模糊形状的应用程序,与阴影最相似的视觉方法是将 globalCompositeOperation 设置为“更轻”并使用带有 globalAlpha 值的 drawImage() 重复绘制预先绘制的径向渐变,或者在需要时绘制单独的渐变是不同的颜色。这不是重叠阴影的完美匹配,但它很接近并且避免了按像素计算。(但是,请注意,在移动 Safari 中,直接绘制阴影填充弧实际上比渐变和 drawImage() 更快。)虽然将 globalCompositeOperation 设置为“lighter”,但使用径向渐变会导致 IE 11 和 Edge 在绘制弧时慢约 10 倍仍然比在所有主要桌面浏览器中使用阴影填充弧更快,
结论
如果您唯一的目标平台是 iPad/iPhone,那么获得漂亮混合形状的最快方法是阴影填充弧。否则,到目前为止,我发现在所有主要桌面浏览器中都可以使用的具有可比外观的最快方法是绘制径向渐变,并将 globalCompositeOperation 设置为“更轻”并使用 globalAlpha 控制不透明度。
注意:在我执行的绘图测试中,有一些明显的方法可以提高性能。特别是,将每个形状的每个实例都绘制到屏幕外缓冲区,然后将整个缓冲区绘制到可见画布上一次,这将产生显着的性能改进。但这会否定此测试的目标,即比较在可见画布上绘制不同类型形状的相对速度。