什么是在HTML 5画布上绘图的最快方法?

17

我正在研究使用HTML的画布作为游戏显示媒介的可能性。举个例子,我需要从许多等角瓦片中构建游戏环境。当然,在2D工作意味着它们必须以矩形包裹的方式出现,因此瓦片之间存在大量重叠。

我已经很老了,自然解决这个问题的方法是调用BitBltMasked。哦,等等,HTML画布没有像BitBlt那么简单且令人愉悦的东西。似乎将像素数据倒入画布的唯一方法要么使用drawImage(),但它没有有用的绘图模式可以忽略alpha通道,要么使用ImageData对象,该对象将图像数据存储在数组中...每次都会对边界进行检查,因此速度非常慢。

好吧,这更像是牢骚而不是一个问题(W3C喜欢的东西通常会引起我的不满),但我真正想知道的是如何快速绘制到画布上?我发现很难放弃这样一个感觉,即每秒执行数百个保留alpha通道的drawImages()本质上是罪恶的,并可能使我的应用程序在许多浏览器中运行缓慢。另一方面,实现BitBlt的唯一途径是严重依赖于浏览器使用类似热点的执行技术来快速运行它。

是否有任何方法可以在每种可能的实现中快速绘制,还是我必须忘记性能?


什么是bitbit?我不算年轻,但不知道它是什么。我在谷歌上搜索了一下,但找到的都是随意和无用的东西。更重要的是,听起来你的问题是你有一个很多动作的游戏。做像渲染背景和缓存它这样的事情有帮助吗?我想如果你解释一下你的设计目标/约束的细节可能会有所帮助。 - Aerik
在我看来,这似乎相当于过早优化。先尝试一个小的测试用例并进行基准测试。 - Hello71
4
呃,你说优化,我说谨慎。我们说的是每秒要做上百个alpha混合,而实际上我需要做的真正数量是零。Alpha混合并不便宜。更不用说,我要用什么来对其进行基准测试呢?整个问题在于,我无法一般化地考虑API的性能表现。我怀疑在Windows上某些浏览器会做得非常快,因为直接绘制为硬件加速提供了一个一致的抽象层。另一方面,我打赌智能手机做这个操作会很慢。 - Chris Davies
@ChrisDavies 你是对的:只有更高级别的操作是可能的,没有 bitblt 可供使用 :) - oberhamsi
[...] drawImage() 没有有用的绘图模式。虽然这是一个老问题,但现在有一个 globalCompositeOperation 属性可以改变绘制模式。它可能会很有用。 - Sebastien C.
显示剩余4条评论
3个回答

6

这是一个非常有趣的问题,你可以做一些有趣的事情来解决它。

首先,你应该知道 drawImage 可以接受 Canvas,不仅仅是图片。这些“子 Canvas”甚至不需要在 DOM 中。这意味着你可以在一个 Canvas 上进行一些组合操作,然后将其绘制到另一个 Canvas 上。这为优化提供了整个世界的机会,特别是在等距瓷砖的背景下。

假设你有一个 50 瓦片长乘以 50 瓦片宽的区域(出于我的理智考虑,我将使用米作单位)。你可以将该区域分成 10x10 米的块。每个块由其自己的 Canvas 表示。要绘制完整场景,你只需将每个块的 Canvas 对象绘制到向用户显示的主 Canvas 上。如果只有四个块(一个 20x20 米的区域),你只需要执行四个 drawImage 操作。

当然,每个单独的块都需要渲染自己的 Canvas。在游戏 ticks 中,如果块中没有发生任何事情,你就什么也不用做:Canvas 将保持不变,并像预期的那样绘制。当发生变化时,根据你的游戏,你可以采取以下几种方法之一:

  1. 如果您的瓷砖延伸到第三维(即:您有一个Z轴),则可以将每个“层”绘制到自己的画布中,并仅更新需要更新的层。例如,如果每个块包含十个深度层,则会有十个画布对象。如果第6层的某个元素被更新,您只需要重新绘制第6层的画布(可能是每平方米一个drawImage,即100个),然后在块中的每层(10个)上执行一个drawImage操作来重新绘制块的画布。根据您在游戏中对环境进行的更新数量,减少或增加块大小可能会增加或降低性能。还可以进行进一步优化,以消除遮挡瓦片等的drawImage调用。
  2. 如果没有第三维,您只需执行每个块的每平方米一个drawImage。如果更新了两个块,则每个时刻只需200个drawImage调用(加上屏幕上可见的每个块一个调用)。如果您的游戏涉及非常少的更新,则缩小块的大小将进一步降低调用次数。
  3. 您可以在它们自己的游戏循环中对块执行更新。如果您正在使用requestAnimationFrame(应该这样做),则只需要将块画布对象绘制到屏幕上。独立地,您可以在setTimeout循环或类似的逻辑中执行游戏逻辑。然后,每个块可以在其自己的帧之间进行更新,而不影响性能。这也可以在Web Worker中使用getImageDataputImageData来发送渲染后的块回主线程,每当需要更新时,但要使其无缝运行将需要大量的努力。

您还可以选择使用像pixi.js这样的库使用WebGL来渲染场景。即使对于2D,它也会通过减少CPU需要执行的工作并将其转移给GPU来增加性能。我强烈建议您去了解一下。


1

我知道GameJS有blit操作,我肯定其他HTML5游戏库也有(gameQuery,LimeJS等等)。我不知道这些包是否已经解决了你所担心的特定数组边界检查问题,但在实践中,它们的示例似乎在所有平台上都能够快速运行。

你不应该对什么样的加速有假设。例如,GameJS开发人员报告说他将要实现脏矩形跟踪,但事实证明现代浏览器会自动处理这个问题---link

出于这个原因和其他原因,我建议先让东西正常工作,然后再考虑速度。此外,利用绘图库,因为作者可能已经花费了一些时间来优化性能。

我没有个人了解,但你可以研究appMobi的“direct canvas”HTML元素,据称它是普通画布的更快版本,link。我对它是否适用于所有浏览器或只适用于Webkit浏览器或只适用于appMobi自己的特殊浏览器感到困惑。

再次强调,如果没有对Web浏览器内部进程有深入的了解,就不应该对加速效果做出任何假设。关于“直接画布”的网页提到了许多减慢画布绘制速度的因素:“重新排版文本、映射热点、为参考链接创建索引等等。”Alpha混合和数组边界检查并未被提及为主要导致缓慢的原因!


我是一个 GameJs 的贡献者。blit 只是在底层执行 drawImage()。但你说得对:不要对性能做出假设。浏览器很聪明,JS 编译器更聪明 :) - oberhamsi
在我的JavaScript地图编辑器中,即使只是绘制矩形和线条,也会出现延迟。无法想象在整个地图上使用“drawImaging”。 - Tomáš Zato

0

很遗憾,无法避免 alpha 合成开销。剪切可能是一种解决方案,但我怀疑是否会有太多性能提升,更不用说在不规则形状上实现这样的路线有多么复杂了。

当你必须绘制整个显示屏时,你将不得不处理性能损失。虽然之后,你会有一个整个屏幕预先计算好的 alpha 图像数据,你可以在一个 drawImage 调用中以偏移量绘制此图像数据。然后,你只需要单独绘制滚动到视图中的新瓷砖。

但仍然存在问题,浏览器必须在画布中的不同位置重新绘制每个像素。这相当昂贵。如果有一种方法可以仅滚动像素就好了,但也没有这样的运气。

一个想法是,你可以实现多个画布,每个单独的 canvas 都进行平移,而不是重新绘制像素。这将允许浏览器以更本地的方式决定如何重新绘制这些像素,至少在理论上是这样的。然后,你可以在新的、已使用/缓存的画布元素上呈现新可见的瓷砖。将其定位以与上一屏幕渲染相匹配。

但那只是我的两个比特...我是说位...呃,我的意思是几分钱 :]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接