【问题标题】:Optimising the drawing of overlapping rectangles优化重叠矩形的绘制
【发布时间】:2012-12-28 05:10:38
【问题描述】:

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

改变“颜色”的成本相当高;连续绘制两个蓝色矩形比绘制两个不同颜色的矩形要快得多。

绘制甚至不在屏幕上的矩形的成本也很高,应该避免。

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

例如:

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

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

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

如何以合理的顺序绘制矩形以最小化颜色变化,并在 JavaScript 剔除代码与让 GPU 剔除之间取得良好的折衷?

(仅计算出哪些矩形重叠,哪些是可见的很昂贵。我有一个basic quadtree,这极大地加快了我的场景绘制速度(与仅发出整个场景的绘制操作相比);现在的问题是如何最小化OpenGL状态变化和尽可能串联属性数组)

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

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

事实证明,制作一个具有足够状态更改的小测试应用程序来实际重现我在完整游戏中看到的问题是很困难的。在某些时候,您将不得不认为状态更改可能足够昂贵。同样重要的是如何加快空间索引(演示中的四叉树)和整体方法。

【问题讨论】:

  • 你有多少个矩形?最大可能值。
  • 我的脑袋:从拓扑排序的升序 z 顺序开始,只考虑重叠的矩形对。以重叠矩形的角为坐标,将重叠矩形分割成2或3个相同颜色的小矩形,其中一个将被完全遮挡(立即丢弃)或部分遮挡(部分可以稍后丢弃) .更新拓扑排序关系。重复直到拓扑排序关系为空。那时,不会有重叠,所以用相同的颜色来绘制。矩形的最终数量应该与原始数量成线性关系。
  • @Will 您能否为我们提供您当前方法的独立示例,以便我们进行基准测试和比较改进?
  • 那么适当的指标是简单地绘制所有矩形所需的颜色更改次数,还是有更复杂的优化?
  • “最小化颜色变化”的目标很明确,但不清楚“剔除不可见的矩形”应该是什么意思。后者似乎是先于前者的一步。您是否有一个表示视图的轴对齐边界框,并且想要找到一个很好的数据结构来快速剔除完全在此 AABB 之外的框?四叉树似乎非常适合这一步。

标签: javascript algorithm math webgl spatial-index


【解决方案1】:

选择颜色,而不是盒子!

在任何时间点,一个或多个盒子将是可绘制的,也就是说,它们可以在接下来被绘制而不会产生问题(尽管可能会因为与大多数盒子的颜色不同而产生成本)最近画的盒子)。

每个点的问题都是:接下来我们应该选择什么颜色来绘制?没有必要考虑选择单独的可绘制框来绘制,因为一旦你选择了一个特定的框来绘制下一个,你还不如绘制当时可以绘制的所有可用的相同颜色的框。那是因为绘制一个框永远不会为问题添加约束,它只会删除它们;并且在不更改当前颜色的情况下选择不绘制可绘制的盒子并不能使解决方案的成本低于其他方式,因为您稍后必须绘制此盒子并且可能需要更改颜色。这也意味着我们绘制相同颜色的可绘制盒子的顺序无关紧要,因为我们将在一个盒子绘制操作“块”中一次绘制所有这些盒子。

依赖图

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

此外,当一个给定颜色的盒子只有与其祖先颜色相同的盒子时,它将被绘制在同一个“块”中——因为所有这些祖先都可以在不改变颜色的情况下在它之前绘制。

加速

为了减少计算量,请注意,当某个特定颜色的所有可绘制框都没有不同颜色的后代时,绘制该颜色不会为其他框打开任何可绘制的新机会,因此我们不需要在考虑下一步要绘制哪种颜色时要考虑这种颜色——我们总是可以把它留到以后,而不会有增加成本的风险。事实上,更好 等到以后再绘制这种颜色,因为到那时其他这种颜色的盒子可能已经可以绘制了。如果至少有一个该颜色的可绘制框具有不同颜色的后代,则调用该颜色有帮助。当我们到达没有剩余有用颜色的地步时(即,当所有剩余的盒子只重叠相同颜色的盒子,或者根本没有盒子)时,我们就完成了:只需绘制每种剩余颜色的盒子,在任何顺序。

算法

这些观察结果提出了两种可能的算法:

  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 来创建一种动态编程算法,每当两种不同的颜色选择排列生成相同的图形时,该算法可以节省时间,这种情况经常发生。但可能即使在 DP 之后,该算法也可能需要一定数量的时间(以及空间),这与盒子的数量呈指数关系......我会考虑是否可以找到更好的界限。

【讨论】:

  • 可绘制框和依赖图的好主意。然而,我的直觉告诉我,构建图表会很昂贵。贪心算法可以简单地被证明是次优的。递归算法也可能很昂贵。总而言之,这个解决方案可能不是 js 和 gpu 工作之间的良好权衡。代码应证明。
  • @ilmiacs:在我看来,几乎所有提供算法优化的方法都需要构建该图。但是无论您是否将其存储在特定的数据结构中,您都需要在任何情况下完成这项工作,因此创建图表不应为任何必须了解哪些框相互重叠的方法增加太多开销。但我同意这里的递归算法对于合理大小的数据集来说几乎肯定过于昂贵。
  • @Will:我还没有看过你的样本(也许今晚从家里,也许直到周末。)但是如果你有一个树结构,它不是已经封装了重叠?如果是这样,那么您就是最接近图表的方式。如果你只需要 j_random 的贪心算法来达到你想要的性能,我实际上不会感到惊讶。我也不会惊讶地发现实际的优化问题是 NP 完全的。有那种问题的感觉。
  • @Will '你的华夫饼'真的有必要吗?
  • 似乎我的评论因冒犯而被删除。让我再试一次:“@Will:我的华夫饼?为了回答你的问题,不,我不会为你提供演示代码。看看你能不能找出原因。”
【解决方案2】:

您做出了非常错误的假设,即您在桌面浏览器上获得的性能会以某种方式决定您 iPhone 上的性能。您需要了解 iPhone 硬件实现了tile-based deferred rendering,这意味着无论如何,片段着色器在流水线中的使用非常晚。正如 Apple 自己所说(“不要浪费 CPU 时间从前到后对对象进行排序”),对您的图元进行 Z 排序只会给您带来一点性能提升。

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

【讨论】:

  • 我认为,因为他使用带有“颜色”的引号,所以他指的不是简单的颜色,而是可能是其他状态,例如不同的着色器..
  • @JariKomppa 当然,我也是这么理解的;这不会阻止合并那些不同的着色器。我会澄清我的答案。
  • @SamHocevar 是的,问题是如何合并那些可以以相同状态绘制的东西,从而最小化批次......我不能轻易地绘制不同类型的粒子使用相同的着色器效果或使用相同的着色器绘制网格,但我可以尝试使用相同的着色器同时绘制尽可能多的相同类型的粒子效果,而不是在它们之间翻转......
  • @Will 你需要提供更多关于你想要做什么的信息,因为使用一个大的if() 来合并几个着色器肯定是微不足道的。如果“颜色”的概念延伸到渲染状态,那么这根本不是同一个问题,尤其是在涉及 alpha 混合的情况下。
  • @Will 是的,这是一大堆要解决的新问题和要做出的妥协 :-) 但是根据您提供的信息,您只会获得一般的改进策略,而不是全功能解决方案。
【解决方案3】:

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

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

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

【讨论】:

    【解决方案4】:

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

    这与交换项目对的排序算法有一些关系,除了我们无法比较项目外,我们需要遍历整个列表并计算颜色变化。这在开始时会表现得很糟糕,但会相对较快地收敛到一个好的顺序,并且会适应场景的变化。我认为每帧都计算一个最佳顺序可能不值得;只需很少的额外工作,这将获得并维持近乎最佳的订单。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-09
      • 1970-01-01
      • 1970-01-01
      • 2013-04-22
      • 1970-01-01
      • 2016-10-28
      • 2010-10-08
      相关资源
      最近更新 更多