通常情况下,在玩 2D 游戏或渲染 HTML5 画布时,需要执行优化,以便使用多个层来构建一个合成的场景。在 OpenGL 或 WebGL 等低级别渲染中,通过逐帧地清理和绘制场景来执行渲染。实现渲染之后,需要优化游戏,以减少渲染的量,所需成本因情况而异。因为画布是一个 DOM 元素,它使您能够对多个画布进行分层,以此作为一种优化方法。
CSS: Cascading Style Sheets(级联样式表)
DOM: Document Object Model(文档对象模型)
HTML: HyperText Markup Language(超文本标记语言)
本文将探讨对画布进行分层的合理性。了解 DOM 设置,从而实现分层的画布。使用分层进行优化需要各种实践。本文还将探讨一些优化策略的概念和技术,它们扩展了分层方法。
选择最佳优化策略可能很难。在选择分层的场景时,需要考虑场景是如何组成的。大屏幕上固定物的渲染经常需要重用若干个组件,它们是进行研究的极佳候选人。视差或动画实体等效果往往需要大量的变化的屏幕空间。在探索您的最佳优化策略时,最好注意这些情况。虽然画布的分层优化需要采用几种不同的技术,但在正确应用这些技术后,往往会大幅提升性能。
在使用分层的方法时,第一步是在 DOM 上设置画布。通常情况下,这很简单,只需定义画布元素,将其放入 DOM 中即可,但画布层可能需要一些额外的样式。在使用 CSS 时,成功地实现画布分层有两个要求:
各画布元素必须共存于视区 (viewport) 的同一位置上。
每个画布在另一个画布下面必须是可见的。
图 1显示了层设置背后的通用重叠概念。
设置层的步骤如下:
将画布元素添加到 DOM。
添加画布元素定位样式,以便支持分层。
样式化画布元素,以便生成一个透明的背景。
在 CSS 中创建一个重叠堆栈 (overlay stack) 可能需要少量的样式。使用 HTML 和 CSS 有许多方法进行重叠。本文中的示例使用一个<div>标签来包含画布。<div>标签指定了一个惟一 ID,它将样式应用于其子 HTML5 画布元素,如清单 1所示。
容器<div>通过将所有子画布元素样式化为使用绝对定位来完成重叠要求。通过选择让#viewport使用相对定位,您可以适应未来的发展,因此,应用于子样式的绝对布局样式将会是相对于#viewport容器的样式。
这些 HTML5 画布元素的顺序也很重要。可以按元素出现在 DOM 上的顺序进行顺序管理,也可以按照画布应该显示的顺序来样式化 z-index 样式,从而管理顺序。虽然并非总是如此,但其他样式可能也会影响渲染;在引入额外的样式(比如任何一种 CSS 转换)时要小心。
通过使用重叠可见性来实现层技术的第二个样式要求。该示例使用这个选项来设置 DOM 元素背景颜色,如清单 2所示。
将画布样式化为拥有一个透明背景,这可以实现第二个要求,即拥有可见的重叠画布。现在,您已经构造了标记和样式来满足分层的需要,所以您可以设置一个分层的场景。
在选择优化策略时,应该注意使用该策略时的所有权衡。对 HTML5 画布场景进行分层是一个侧重于运行时内存的策略,用于获得运行时速度方面的优势。您可以在页面的浏览器中增加更多的权重,以获得更快的帧速率。一般来说,画布被视为是浏览器上的一个图形平面,其中包括一个图形 API。
通过在 Google Chrome 19 进行测试,并记录浏览器的选项卡内存使用情况,您可以看到内存使用的明显趋势。该测试使用了已经样式化的<div>
(正如上一节中讨论的那样),并生成了放置在<div>上的用单一颜色填充的画布元素。画布的大小被设定为 1600 x 900 像素,并从 Chrome1 的任务管理器实用程序收集数据。表 1显示了一个示例。
在 Google Chrome 的 Task Manager 中,您可以看到某个页面所使用的内存量(也称为 RAM)。Chrome 也提供 GPU 内存,或者是 GPU 正在使用的内存。这是常见信息,如几何形状、纹理或计算机将您的画布数据推送到屏幕可能需要的任何形式的缓存数据。内存越低,放在计算机上的权重就会越少。虽然目前还没有任何确切的数字作为依据,但应始终对此进行测试,确保您的程序不会超出极限,并使用了过多的内存。如果使用了过多的内存,浏览器或页面就会因为缺乏内存资源而崩溃。GPU 处理是一个远大的编程追求,已超出本文的讨论范围。您可以从学习 OpenGL 或查阅 Chrome 的文档(请参阅参考资料)开始。
在表 1中,随着在页面上引入和使用了更多的 HTML5 画布元素,使用的内存也越多。一般的内存也存在线性相关,但每增加一层,内存的增长就会明显减少。虽然这个测试并没有详细说明这些层对性能带来的影响,但它确实表明,画布会严重影响 GPU 内存。一定要记得在您的目标平台上执行压力测试,以确保平台的限制不会导致您的应用程序无法执行。
当选择更改某个分层解决方案的单一画布渲染周期时,需考虑有关内存开销的性能增益。尽管存在内存成本,但这项技术可以通过减小每一帧上修改的像素数量来完成其工作。
下一节将说明如何使用分层来组织一个场景。
在本节中,我们将通过重构一个滚动平台跑步风格的游戏上的视差效果的单画布实现,了解一个多层解决方案。图 2显示了游戏视图的组成,其中包括云、小山、地面、背景和一些交互实体。
在游戏中,云、小山、地面和背景都以不同的速度移动。本质上,背景中较远的元素移动得比在前面的元素慢,因此形成了视差效果。为了让情况变得更为复杂,背景的移动速度会足够慢,它每半秒钟才重新渲染一次。
通常情况下,好的解决方案会将所有帧都清除并重新渲染屏幕,因为背景是一个图像并且在不断变化。在本例中,由于背景每秒只需变化两次,所以您不需要重新渲染每一帧。
目前,您已经定义了工作区,所以可以决定场景的哪些部分应该在同一个层上。组织好各个层之后,我们将探讨用于分层的各种渲染策略。首先,需要考虑如何使用单个画布来实现该解决方案,如清单 3所示。
像清单 3中的代码一样,该解决方案会有一个render函数,每个游戏循环调用或每个更新间隔都会调用它。在本例中,渲染是从主循环调用和更新每个元素的位置的更新调用中抽象出来。
遵循 “清除到渲染” 解决方案,render会调用清除上下文,并通过调用屏幕上的实体各自的render函数来跟踪它。清单 3遵循一个程序化的路径,将元素放置到画布上。虽然该解决方案对于渲染屏幕上的实体是有效的,但它既没有描述所使用的所有渲染方法,也不支持任何形式的渲染优化。
为了更好地详细说明实体的渲染方法,需要使用两种类型的实体对象。清单 4显示了您将使用和细化的两个实体。
清单 4中的对象存储实体的图像、x、y、宽度和高度的实例变量。这些对象遵循 JavaScript 语法,但为了简洁起见,仅提供了目标对象的不完整的伪代码。目前,渲染算法非常贪婪地在画布上渲染出它们的图像,完全不考虑游戏循环的其他任何要求。
为了提高性能,需要重点注意的是,panning渲染调用输出了一个比所需图像更大的图像。本文忽略这个特定的优化,但是,如果使用的空间比您的图像提供的空间小,那么请确保只渲染必要的补丁。
现在您知道如何使用单一画布实现该示例,让我们看看有什么办法可以完善这种类型的场景,并加快渲染循环。要使用分层技术,则必须通过找出实体的渲染重叠,识别分层所需的 HTML5 画布元素。
为了确定是否存在重叠,要考虑一些被称为重绘区域的不可见区域。重绘区域是在绘制实体的图像时需要画布清除的区域。重绘区域对于渲染分析很重要,因为它们使您能够找到完善渲染场景的优化技术,如图 3所示。
为了可视化图 3中的效果,在场景中的每个实体都有一个表示重绘区域的重叠,它跨越了视区宽度和实体的图像高度。场景可分为三组:背景、前景和交互。场景中的重绘区域有一个彩色的重叠,以区分不同的区域:
背景 – 黑色
云 – 红色
小山 – 绿色
地面 – 蓝色
红球 – 蓝色
黄色障碍物 – 蓝色
对于除了球和障碍物以外的所有重叠,重绘区域都会横跨视区宽度。这些实体的图像几乎填满整个屏幕。由于它们的平移要求,它们将渲染整个视区宽度,如图 4所示。预计球和障碍物会穿过该视区,并且可能拥有通过实体位置定义的各自的区域。如果您删除渲染到场景的图像,只留下重绘区域,就可以很容易地看到单独的图层。
初始层是显而易见的,因为您可以注意到互相重叠的各个区域。由于球和障碍物区域覆盖了小山和地面,所以可将这些实体分组为一层,该层被称为交互层。根据游戏实体的渲染顺序,交互层是顶层。
找到附加层的另一种方法是收集没有重叠的所有区域。占据视区的红色、绿色和蓝色区域并没有重叠,并且它们组成了第二层——前景。云和交互实体的区域没有重叠,但因为球有可能跳跃到红色区域,所以您应该考虑将该实体作为一个单独的层。
对于黑色区域,可以很容易地推断出,背景实体将会组成最后一层。填充整个视区的任何区域(如背景实体)都应视为填充整个层中的该区域,虽然这对本场景并不适用。在定义了我们的三个层次之后,我们就可以开始将这层分配给画布,如图 5所示。
现在已经为每个分组的实体定义了层,现在就可以开始优化画布清除。此优化的目标是为了节省处理时间,可以通过减少每一步渲染的屏幕上的固定物数量来实现。需要重点注意的是,使用不同的策略可能会使图像获得更好的优化。下一节将探讨各种实体或层的优化方法。