优化重叠矩形的绘制

时间:2013-01-14 08:28:39

标签: javascript algorithm math webgl spatial-index

我有大量的矩形,有些与其他矩形重叠;每个矩形都有一个绝对的z顺序和一个颜色。 (每个'矩形'实际上是粒子效果,网格或纹理的轴对齐边界框,可能是半透明的。但只要你不试图剔除其他人的矩形,它就更容易抽象地思考彩色矩形。 ,所以我将在问题描述中使用它:)

改变'颜色'的成本非常高;连续绘制两个蓝色矩形比绘制两个不同颜色的矩形要快得多。

绘制在屏幕上不均匀的矩形的成本也非常高,应该避免。

如果两个矩形不重叠,则相对于彼此绘制的顺序并不重要。只有当它们重叠时,z次序才是重要的。

例如:

Overlapping Rectangles

1(红色)和4(红色)可以一起绘制。 2(蓝色)和5(蓝色)也可以一起绘制,3(绿色)和7(绿色)也可以绘制在一起。但是必须在6(蓝色)之后绘制8(红色)。所以要么我们将所有三个红色绘制在一起并将两个蓝色绘制成蓝色,或者我们将所有蓝色绘制在一起并将红色绘制成两组。

有些矩形可能会偶尔移动。 (并非全部;已知某些矩形是静态的;已知其他矩形会移动。)

我将在JavaScript / webGL中绘制这个场景。

如何以合理的顺序绘制中的矩形以最小化颜色变化,与JavaScript剔除代码相比,与让GPU剔除相比有了很好的权衡?

(只是确定哪些矩形重叠并且哪些是可见的是昂贵的。我有一个basic quadtree并且这加速了我的场景极大地绘制(与刚刚为整个场景发射绘图操作相比);现在问题是如何最小化OpenGL状态变化并尽可能地连接属性数组

更新 我创建了一个非常简单的测试应用来说明问题,并作为演示解决方案的基础:http://williame.github.com/opt_rects/

源代码在github上,可以很容易地分叉:https://github.com/williame/opt_rects

事实证明很难制作一个具有足够状态变化的小测试应用程序来实际重现我在完整游戏中看到的问题。在某些时候,你必须把它当作一个给定的状态变化可能足够昂贵。同样重要的是如何加速空间索引(演示中的四叉树)和整体方法。

4 个答案:

答案 0 :(得分:16)

您正在做出错误的假设,即您将在桌面浏览器上获得的性能将以某种方式决定iPhone的性能。您需要了解iPhone硬件实现tile-based deferred rendering,这意味着无论如何片段着色器在管道中使用得很晚。正如Apple自己所说的那样(“不要浪费CPU时间从前到后排序对象”),对原语进行Z排序会让你获得很少的性能提升。

但是这是我的建议:如果改变颜色很贵,只是不改变颜色:将它作为顶点属性传递,并将行为合并到一个超级着色器中,这样你就可以绘制所有颜色在一个或几个批次中甚至没有排序。然后对您的平台进行基准测试并确定最佳批量大小。

答案 1 :(得分:12)

选择颜色,而不是盒子!

在任何时间点,一个或多个盒子都可以 paintable ,即它们可以在不引入问题的情况下进行涂漆(尽管由于颜色不同而可能会产生成本最近画的盒子)。

每个方面的问题是:我们应该选择下一步的颜色?没有必要考虑选择绘制单个可绘制的盒子,因为只要你选择一个特定的盒子来绘制下一个,你也可以绘制当时可以绘制的相同颜色的所有可用盒子。那是因为绘制一个框永远不会为问题添加约束,它只会删除它们;并且如果不改变当前的颜色而选择不绘制可涂漆的盒子,则无法使解决方案比原本更便宜,因为您以后必须对此盒子进行涂漆,这可能需要更换颜色。这也意味着我们绘制相同颜色的可绘制盒子的顺序并不重要,因为我们将在一个“块”的盒子绘画操作中一次性绘制所有这些盒子。

依赖图

首先构建一个“位于下面”的依赖图,其中每个彩色矩形由顶点表示,如果矩形v与矩形u重叠并位于其下方,则从v到u有一个弧(箭头)。我的第一个想法是通过查找传递闭包来使用它来构建“必须绘制之前”依赖图,但实际上我们不需要这样做,因为下面关注的所有算法都是顶点是否可绘制。可绘制顶点是没有前驱(弧内)的顶点,并且采用传递闭包不会改变顶点是否具有0个圆弧。

此外,每当给定颜色的盒子只有与其祖先颜色相同的盒子时,它将被涂在相同的“块”中 - 因为所有这些祖先都可以在它之前被涂上而不改变颜色。 / p>

加速

为了减少计算量,请注意,每当所有特定颜色的可涂抹盒子都没有不同颜色的后代时,涂上这种颜色不会为其他盒子开辟任何新的机会,因此我们不需要在考虑接下来要涂漆的颜色时要考虑这种颜色 - 我们总是可以将它留到以后,而不会增加成本。事实上,将这种颜色留下来直到以后才能更好,因为到那时,这种颜色的其他盒子可能已经变得可以涂漆了。如果至少有一个具有不同颜色后代颜色的可绘制框,则调用颜色有用。当我们到达没有有用颜色的时候(即当所有剩余的盒子只重叠相同颜色的盒子,或根本没有盒子)时,我们就完成了:只需绘制每个剩余颜色的盒子,选择颜色任何订单。

算法

这些观察结果表明了两种可能的算法:

  1. 一种快速但可能不太理想的贪婪算法:选择接下来绘制产生最新可绘制顶点的颜色。 (这将自动仅考虑有用的颜色。)
  2. 较慢,精确的DP或递归算法:对于每种可能有用的颜色c,请考虑下一步绘制所有可涂漆的C色框所产生的依赖关系图:

    设f(g)是绘制依赖图g中所有框所需的最小颜色变化数。然后

    f(g)= 1 + min(f(p(c,g)))

    对于所有有用的颜色c,其中p(c,g)是通过绘制所有颜色c的可绘制框而产生的依赖图。如果G是原始问题的依赖图,则f(G)将是最小变化数。可以通过向后追踪DP成本矩阵来重建颜色选择本身。

    f(g)可以memoised创建一个动态编程算法,每当2种颜色选择的不同排列产生相同的图形时,可以节省时间,这种情况经常会发生。但也许甚至在DP之后,这个算法可能需要花费一定时间(因此空间)的数量是指数的盒子......我将考虑是否可以找到更好的界限。

答案 2 :(得分:2)

这是一种可能性。你必须对它进行基准测试,看看它是否真的是一种改进。

For all rectangles, back to front:
  If this rectangle has been marked as drawn, skip to the next one
  Set a screen-sized unseen surface to all black
  Call this rectangle's color "the color"
  For rectangles starting with this one and proceeding toward the front
    If (this rectangle's color is the color and
        all the pixels of this rectangle on the unseen are black) then
      Add this rectangle to the to-draw list
    Draw a white rectangle with this rectangle's shape on the unseen surface
    If the unseen surface is more than half white, break
  For all rectangles on the to-draw list:
    Draw the rectangle
    Mark it as drawn

不能保证在排序方面是最优的,但我认为它会非常接近,而且在预拉伸步骤中它是最坏情况的二次方。它确实依赖于图形缓冲区的快速回读。可能有帮助的一个技巧是创建一个新的像素表面,它是感兴趣区域的缩小版本。它的颜色将是原始的白色部分。

答案 3 :(得分:2)

首先以随机(但正确)的顺序绘制,例如以严格的z顺序。绘制每个帧时,要么计算颜色变化的数量,要么计算完整帧的实际时间。每帧,尝试交换两个矩形的顺序。要交换的矩形不得重叠,因此可以按任何顺序绘制它们而不违反正确性;除此之外,它们可以随机选择,或者通过列表进行线性传递,或者......如果进行交换减少了颜色变化的数量,请保留新的顺序,如果没有还原它,请尝试不同的交换。下一帧。如果进行交换既不减少也不增加颜色变化的数量,保持50%的几率。对于在前一帧中没有重叠但由于移动而开始重叠的任何矩形,只需将它们交换为z顺序。

这与交换项目对的排序算法有一定关系,除了我们无法比较项目,我们需要遍历整个列表并计算颜色变化。这将首先表现得非常糟糕,但相对快速地收敛到良好的秩序,并将适应场景变化。我认为通过并计算每帧的最佳顺序可能是不值得的;这将使得并保持近乎最优的顺序而只需要很少的额外工作。

参考您的绘图:随机选取的初始绘制顺序:1,6,2,4,5,8,3,7(5种颜色变化)。交换5,8。新订单:1,6,2,4,8,5,3,7(4种颜色变化)=>保持新秩序。