通常情况下,在玩 2D 游戏或渲染 HTML5 画布时,需要执行优化,以便使用多个层来构建一个合成的场景。在 OpenGL 或 WebGL 等低级别渲染中,通过逐帧地清理和绘制场景来执行渲染。实现渲染之后,需要优化游戏,以减少渲染的量,所需成本因情况而异。因为画布是一个 DOM 元素,它使您能够对多个画布进行分层,以此作为一种优化方法。
常用的缩写
- CSS: Cascading Style Sheets(级联样式表)
DOM: Document Object Model(文档对象模型)
HTML: HyperText Markup Language(超文本标记语言)
本文将探讨对画布进行分层的合理性。了解 DOM 设置,从而实现分层的画布。使用分层进行优化需要各种实践。本文还将探讨一些优化策略的概念和技术,它们扩展了分层方法。
您可以下载在本文中使用的示例的源代码。
选择优化策略
选择最佳优化策略可能很难。在选择分层的场景时,需要考虑场景是如何组成的。大屏幕上固定物的渲染经常需要重用若干个组件,它们是进行研究的极佳候选人。视差或动画实体等效果往往需要大量的变化的屏幕空间。在探索您的最佳优化策略时,最好注意这些情况。虽然画布的分层优化需要采用几种不同的技术,但在正确应用这些技术后,往往会大幅提升性能。
设置层
在使用分层的方法时,第一步是在 DOM 上设置画布。通常情况下,这很简单,只需定义画布元素,将其放入 DOM 中即可,但画布层可能需要一些额外的样式。在使用 CSS 时,成功地实现画布分层有两个要求:
各画布元素必须共存于视区 (viewport) 的同一位置上。
每个画布在另一个画布下面必须是可见的。
图 1显示了层设置背后的通用重叠概念。
图 1. 层示例
设置层的步骤如下:
- 将画布元素添加到 DOM。
添加画布元素定位样式,以便支持分层。
样式化画布元素,以便生成一个透明的背景。
设置画布重叠堆栈
在 CSS 中创建一个重叠堆栈 (overlay stack) 可能需要少量的样式。使用 HTML 和 CSS 有许多方法进行重叠。本文中的示例使用一个
- #viewport {
- /**
- * Position relative so that canvas elements
- * inside of it will be relative to the parent
- */
- position: relative;
- }
- #viewport canvas {
- /**
- * Position absolute provides canvases to be able
- * to be layered on top of each other
- * Be sure to remember a z-index!
- */
- position: absolute;
- }
容器
这些 HTML5 画布元素的顺序也很重要。可以按元素出现在 DOM 上的顺序进行顺序管理,也可以按照画布应该显示的顺序来样式化 z-index 样式,从而管理顺序。虽然并非总是如此,但其他样式可能也会影响渲染;在引入额外的样式(比如任何一种 CSS 转换)时要小心。
透明的背景
通过使用重叠可见性来实现层技术的第二个样式要求。该示例使用这个选项来设置 DOM 元素背景颜色,如清单 2所示。
清单 2. 设置透明背景的样式表规则
- canvas {
- /**
- * Set transparent to let any other canvases render through
- */
- background-color: transparent;
- }
将画布样式化为拥有一个透明背景,这可以实现第二个要求,即拥有可见的重叠画布。现在,您已经构造了标记和样式来满足分层的需要,所以您可以设置一个分层的场景。
分层方面的考虑因素
在选择优化策略时,应该注意使用该策略时的所有权衡。对 HTML5 画布场景进行分层是一个侧重于运行时内存的策略,用于获得运行时速度方面的优势。您可以在页面的浏览器中增加更多的权重,以获得更快的帧速率。一般来说,画布被视为是浏览器上的一个图形平面,其中包括一个图形 API。
通过在 Google Chrome 19 进行测试,并记录浏览器的选项卡内存使用情况,您可以看到内存使用的明显趋势。该测试使用了已经样式化的
在 Google Chrome 的 Task Manager 中,您可以看到某个页面所使用的内存量(也称为 RAM)。Chrome 也提供 GPU 内存,或者是 GPU 正在使用的内存。这是常见信息,如几何形状、纹理或计算机将您的画布数据推送到屏幕可能需要的任何形式的缓存数据。内存越低,放在计算机上的权重就会越少。虽然目前还没有任何确切的数字作为依据,但应始终对此进行测试,确保您的程序不会超出极限,并使用了过多的内存。如果使用了过多的内存,浏览器或页面就会因为缺乏内存资源而崩溃。GPU 处理是一个远大的编程追求,已超出本文的讨论范围。您可以从学习 OpenGL 或查阅 Chrome 的文档(请参阅参考资料)开始。
表 1. 画布层的内存开销
在表 1中,随着在页面上引入和使用了更多的 HTML5 画布元素,使用的内存也越多。一般的内存也存在线性相关,但每增加一层,内存的增长就会明显减少。虽然这个测试并没有详细说明这些层对性能带来的影响,但它确实表明,画布会严重影响 GPU 内存。一定要记得在您的目标平台上执行压力测试,以确保平台的限制不会导致您的应用程序无法执行。
当选择更改某个分层解决方案的单一画布渲染周期时,需考虑有关内存开销的性能增益。尽管存在内存成本,但这项技术可以通过减小每一帧上修改的像素数量来完成其工作。
下一节将说明如何使用分层来组织一个场景。
对场景进行分层:游戏
在本节中,我们将通过重构一个滚动平台跑步风格的游戏上的视差效果的单画布实现,了解一个多层解决方案。图 2显示了游戏视图的组成,其中包括云、小山、地面、背景和一些交互实体。
图 2. 合成游戏视图
在游戏中,云、小山、地面和背景都以不同的速度移动。本质上,背景中较远的元素移动得比在前面的元素慢,因此形成了视差效果。为了让情况变得更为复杂,背景的移动速度会足够慢,它每半秒钟才重新渲染一次。
通常情况下,好的解决方案会将所有帧都清除并重新渲染屏幕,因为背景是一个图像并且在不断变化。在本例中,由于背景每秒只需变化两次,所以您不需要重新渲染每一帧。
目前,您已经定义了工作区,所以可以决定场景的哪些部分应该在同一个层上。组织好各个层之后,我们将探讨用于分层的各种渲染策略。首先,需要考虑如何使用单个画布来实现该解决方案,如清单 3所示。
清单 3. 单画布渲染循环的伪代码
- /**
- * Render call
- *
- * @param {CanvasRenderingContext2D} context Canvas context
- */
- function renderLoop(context)
- {
- context.clearRect(0, 0, width, height);
- background.render(context);
- ground.render(context);
- hills.render(context);
- cloud.render(context);
- player.render(context);
- }
像清单 3中的代码一样,该解决方案会有一个render函数,每个游戏循环调用或每个更新间隔都会调用它。在本例中,渲染是从主循环调用和更新每个元素的位置的更新调用中抽象出来。
遵循 “清除到渲染” 解决方案,render会调用清除