【问题标题】:Large 2D world rendering in HTML5 CanvasHTML5 Canvas 中的大型 2D 世界渲染
【发布时间】:2014-10-02 14:53:47
【问题描述】:

我有一个由随机生成的块组成的世界(黑色为开启,白色为关闭)。缩小时,它基本上看起来像白噪声。但是,它们不是每个块为 1 个像素,而是 40 个像素并绘制为图像纹理。 我的游戏以相机为基础运行,因此您一次只能看到地图的一小部分,您必须四处移动角色才能探索其余部分。

目前,我的游戏只是渲染画布范围内的每个图像(块纹理)。这导致每帧绘制 80-100 张图像。虽然它在台式电脑上运行良好,但在移动设备上却表现不佳。

考虑到地图的外观在整个游戏中都不会改变,我想尝试一种不同的方法。我创建了一个世界大小的画布,最终大小为 1600x24000 像素。我将所有纹理绘制到外部隐藏的画布上。这是在初始化时完成的。然后我会使用drawImage 中的剪辑属性来获取我需要的小节。虽然它有效,但它非常滞后并且使事情变得比以前更糟。此外,图像质量下降到更模糊的外观,这是不可取的。

现在我正在寻找更好的方法来解决这个问题。所以我的问题是,我该怎么做呢?谢谢。

【问题讨论】:

    标签: javascript html canvas html5-canvas


    【解决方案1】:

    当你使用一个巨大的画布时,你不能确定渲染器不会加载整个纹理来渲染它的一部分。由于您看到性能大幅下降,这很可能正在发生。

    我会做的几件事:
    • 仅尝试使用 fillRect 来查看有多少 drawImage 应受到指责。
    • 尝试一劳永逸地设置上下文,然后只使用最简单的drawImage:

    var topLeft = { col:12, row : 6 }; // shift of the left-most rect (indexes)
    context.save();
    context.scale( scale, scale); 
    for  column = 0 to columnSeenCount 
         for row = 0 to rowSeenCount
             image = the image of  ( topLeft.col + column , topLeft.row + row )
             context.drawImage( image, column, row) ;
    context.restore(); 
    

    这样可以避免为每个 drawImage 重新计算变换矩阵。渲染器涉及的数学要少得多。

    • 如果你自己做drawImage,尽量只使用圆角坐标,这样会更快。
    • 您还必须将刻度四舍五入以防止伪影。您可以在 1 上舍入,但对于比例来说,这可能是一个太大的限制:您可以轻松地“舍入”到 0.5 或 0.25 或...通过这样做:

      var precision     = 2 ;     //  0 => floor ; 1 => at 0.5 ; 2 => 0.25 ; .... 
      var factor        = 1 << precision ;
      var roundedFigure = Math.floor( figure * factor) / factor ;
    

    • 如果您的应用程序的完成方式可以很容易地为每个图块类型绘制图块类型,那么这样做可能会赢得一些时间(您将受益于缓存中的图像这一事实)。

    • 之后,您唯一的选择就是使用 webGL 或基于 webGL 的渲染器...

    【讨论】:

    • 感谢您的回复。我已经进行了一些测试,并确定在我的设备(带 Chrome 的 Galaxy Nexus)上绘制图像与简单矩形相比平均慢 6 fps - 所以我会说它肯定会影响性能。恐怕我不太了解您的第一个代码示例。为什么要缩放上下文?除此之外,看起来您正在单独绘制每个图块,就像我现在正在做的一样。如果我错了,请纠正我,但我注意到与画布绘图相比,数学运算几乎不需要时间,因此可以忽略不计。
    • 假设我捕获了所有非整数坐标,我收到的帧速率与以前相同。
    【解决方案2】:

    另外两个想法可以提高你的表现:

    • 检查您的整个世界是否被渲染,或者只是可见图像(在舞台上)。例如,将世界大小加倍,看看它是否会影响性能。如果您只渲染相关图像,则不应该。

    • 使用CocoonJS 编译您的应用程序。它承诺将移动设备的应用程序速度提高 10 倍。但请注意,这意味着对画布周围的 html 有一些严格的限制。


    过时的答案,假设问题是由于缩小太远引起的:

    在 3D 图形中,Mipmaps 可以用来避免这个问题。当物体离相机更远时,基本上会使用更小的图像(即更少的像素)。

    如果你用谷歌搜索类似 html5 canvas 2D Mipmaps 之类的东西,也许你能找到合适的东西。或者您可以自己构建一个简单的 mipmapping 算法。

    但在投入工作之前,通过简单地更改所有块图像(使用 1x1 像素图像)来尝试这种方法的性能。正如您所假设的,也许您的性能问题不是由缓慢的渲染引起的。学习使用分析器,如果它不能解决问题。

    【讨论】:

    • 我想你可能误解了我的情况。我所说的白噪声是对我的大世界的总体概述,假设每个像素代表这个世界中的一个块。
    • 是的,我完全理解这一点。但是你的世界是 1600x24000px 大。另一方面,您只有一个屏幕,例如800x600px 或类似的东西。正如您已经体验到的,绘制所有 1600x24000 像素并按比例缩小它们是非常无效的。相反,您可以预先计算在每个 800x600 像素上绘制的内容,以节省渲染时间。我改写的方法是一种方法,在这种情况下与降噪无关。
    • 我不明白 mipmapping 将如何帮助我。我没有缩小任何东西。 This is an MS-Paint mock up of what my game looks like. 每个黑色块代表一个 40x40px 的纹理。每一帧,我都在屏幕上分别绘制每个块。我正在寻找改善这一点的方法,因为移动性能并不是最好的。如果我再次误解了您的解决方案,我提前道歉。
    • 但是在这个相机距离下你有性能问题吗?据我了解您的问题,当您必须在每个渲染帧中绘制数千个这些块时,性能变得至关重要,因为您的相机缩小了!?我理解正确吗?
    • 是的,在那个缩放级别上,我的 Galaxy Nexus 以及运行 Chrome 的 Galaxy S4 只能达到 40-45 fps。这并不可怕,但我更喜欢更好。但是,我的相机不会缩小或缩小,块始终为 40px,平均每帧绘制 80-100 张图像。我在 jsPerf 中做了很多测试,发现实际图像大小并不重要,因为无论如何它都会放大到 40px。
    【解决方案3】:

    几个问题和想法:

    我会同意@GameAlchemist 的提示,即使用drawImage 的剪辑版本比将单独的平铺图像“blitting”到画布上要慢。当您的地图图像过大时,请使用单独的图像。

    24000 像素的宽度太大,无法包含在任何 1 张图片中。

    看起来您正在水平平移。您可以将 24000 像素宽的图像分割成更合理尺寸的单个图像。每个图像可能是屏幕宽度的 3 倍。当用户平移超出当前图像的边缘时交换图像。

    您使用了多少个唯一块图像图块?

    当您检测到移动用户时,可能会减少唯一图块的数量。然后将每个独特的图块放在单独的图像或画布上。

    您的地图主要是一种图块类型(例如白色/关闭)吗?

    如果是这样,您可以制作一张由足够多的白色瓷砖组成的网格来填充整个画布。然后在必要时添加黑色瓷砖。这会将您的绘图减少为 1 个白色网格图像加上任何所需的黑色图像。

    【讨论】:

    • 是的,我目前为每个平铺纹理使用单独的图像。我将尝试拆分图像。我不确定我是否提到玩家总是停留在屏幕的中心,因此世界围绕着他移动。如果玩家坐在其中一张图片的一角,我将不得不绘制和管理另外 4 张图片来扩展视图。我想知道这是否会导致滞后峰值或根本不值得。我不太明白你所说的独特瓷砖是什么意思。我正在使用大约 15 种不同的纹理可能性,所以最后一个选项不起作用。
    • 另外,您是否了解图像文件类型以及它们如何影响渲染时间?我目前正在使用稍微透明的 PNG。切换到不透明的 jpg 或 bmp 会显着提高性能吗?
    猜你喜欢
    • 1970-01-01
    • 2012-03-23
    • 1970-01-01
    • 2012-04-19
    • 1970-01-01
    • 2011-08-31
    • 2013-09-22
    • 2021-06-12
    • 2015-07-29
    相关资源
    最近更新 更多