【问题标题】:Drawing thousands of shapes with Java2D efficiently?使用 Java2D 高效绘制数千个形状?
【发布时间】:2014-08-19 17:48:01
【问题描述】:

我在使用 Java2D 绘制可能数十万个矩形时遇到问题。

起初我所做的事情是这样的:

private void render() {
   BufferStrategy bs = getBufferStrategy();
   if (bs == null) {
      createBufferStrategy(3);
      return;
   }

   Graphics g = bs.getDrawGraphics();
   //Draw Graphics here
   g.dispose();
   bs.show();
}

然后我在测试后意识到,当任何超过 500 个矩形时,这非常低效,所以我认为也许将图形绘制到 BufferedImage 然后显示 BufferedImage 会更有效,所以我想出了这个。

private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

private void render() {
   BufferStrategy bs = getBufferStrategy();
   if (bs == null) {
      createBufferStrategy(3);
      return;
   }

   Graphics g = image.createGraphics();
   //Draw Graphics here
   g.dispose();

   Graphics g2 = bs.getDrawGraphics();
   g2.drawImage(image, 0, 0, width, height, null);
   g2.dispose();
   bs.show();
}

但是,这仍然与第一种方法一样低效,我很难想出更好的方法来做到这一点,我宁愿远离 OpenGL 之类的东西。

在 500-1000 个矩形之间,它会变得非常慢,超过此范围的任何东西程序都会很快冻结。

所以看起来碰撞检测似乎是主要问题,而不是 Java2D 渲染。这就是我处理检测的方式,它检查两个矩形是否在任何一侧接触。如果有人对此有更好的方法,我将不胜感激,因为我看到这是多么低效。

public boolean collides(Entity e) {
    Point2D upperLeftIn = new Point2D.Double(bounds.getX() + 1, bounds.getY());
    Point2D upperRightIn = new Point2D.Double(bounds.getX() + 8, bounds.getY());
    Point2D lowerLeftIn = new Point2D.Double(bounds.getX() + 1, bounds.getY() + 9);
    Point2D lowerRightIn = new Point2D.Double(bounds.getX() + 8, bounds.getY() + 9);

    Point2D upperLeftDown = new Point2D.Double(bounds.getX(), bounds.getY() + 1);
    Point2D lowerLeftUp = new Point2D.Double(bounds.getX(), bounds.getY() + 8);
    Point2D upperRightDown = new Point2D.Double(bounds.getX() + bounds.getWidth(), bounds.getY() + 1);
    Point2D lowerRightUp = new Point2D.Double(bounds.getX() + bounds.getWidth(), bounds.getY() + 8);

    Line2D top = new Line2D.Double(upperLeftIn, upperRightIn);
    Line2D bottom = new Line2D.Double(lowerLeftIn, lowerRightIn);
    Line2D left = new Line2D.Double(upperLeftDown, lowerLeftUp);
    Line2D right = new Line2D.Double(upperRightDown, lowerRightUp);

    if (e.bounds.intersectsLine(top)) {
        return true;
    }
    if (e.bounds.intersectsLine(bottom)) {
        return true;
    }
    if (e.bounds.intersectsLine(left)) {
        return true;
    }
    if (e.bounds.intersectsLine(right)) {
        return true;
    }

    return false;
}

【问题讨论】:

  • 我有示例,仅使用 Swing 以稳定的 25fps 渲染(和动画)超过 10, 0000 个形状,没什么特别的。你能提供一个可运行的例子来说明你的问题吗?
  • 您的渲染方法多久调用一次?
  • 每秒 60 次。我正在编写一个小示例,稍后发布。
  • 我做了一个小例子,可以毫不费力地绘制和动画 20000 个形状。这个程序还有碰撞检测和其他更新,所以我认为碰撞检测是现在让我慢下来的原因。如果您认为有更好的方法来处理它,我已经编辑了我的帖子以显示我的检测代码(它检查两个矩形是否发生碰撞)。

标签: java java-2d


【解决方案1】:

虽然我不确定如何处理问题的内容/目标由于编辑而完全改变的事实......

总结一下:正如评论中指出的那样(您似乎已经预料到了),即使是数千个矩形的渲染在 Java2D 中也不应该成为一个大问题。在内部,它正在使用 DirectX 或 OpenGL 的硬件支持,您确实必须将大量抗锯齿文本和复杂、纹理或渐变绘制的形状溢出到屏幕上才能真正减慢它的速度。


话虽如此,碰撞检测确实不太可能成为这里的瓶颈。

大概您发布的方法在Entity 类中。据推测,e.boundsRectangle2D。在这种情况下,您可以简单地测试与

的交集
public boolean collides(Entity e) {
    return this.bounds.intersects(e.bounds);
}

不清楚你想通过在那里创建Line2D 对象来实现什么,你可能应该用文字解释一下。 (也许在实际边界附近存在某种“阈值”?)。但是您应该记住,intersectsLine 方法的计算成本可能很高,至少与您实际上想要执行的测试相比。您应该尝试将其归结为间隔检查。


但是,即使您对 collides 方法进行这种(微?-)优化,问题也可能是一个更普遍的问题:从您目前所写的内容来看,您必须假设您正在测试每个实体之间的冲突,并且这个方法在循环中调用,如

for (int i=0; i<allEntities.size(); i++)
{ 
    for (int j=i+1; j<allEntities.size(); j++)
    {
        Entity ei = allEntities.get(i);
        Entity ej = allEntities.get(j);
        if (ei.collides(ej)) handleCollision();
    }
}   

如果是这种情况,实现的优化不会帮助你,因为问题在于渐近复杂性:交集测试的数量(即,对collides-方法的调用次数随着对象的数量二次增长。对于 500 个对象,您必须对方法进行 ~125.000 次调用。这已经很多了,几乎不可能每秒完成 60 次。但是对于 5000 个对象,您不需要 1.250.000 次调用,而是 12.500.000 - 这是 100 倍,当然,在 60 FPS 下是不可能的。

对于这么多对象的(成对)碰撞检测,有复杂的数据结构。不幸的是,这里的“sophisticated”通常意味着“实现起来非常复杂”。 Bounding Volume Hierarchies 可能是一种可以通过合理的努力帮助加速碰撞检测的方法。但是,如果您在“小”空间中有“许多”和“小”对象,则 空间散列 可能是更合适的解决方案。您可以使用此关键字快速找到教程/博客条目和示例代码,其中一个示例位于 Spatial hashing implementation for fast 2D collisions,但还有其他几个。

【讨论】:

  • 感谢您的回答。我从以前的项目中复制了碰撞代码,我需要查看两个矩形在哪一侧发生碰撞,但只涉及 2 个形状,所以它并不密集,我想我只是没有意识到它不适用和这里一样。我通读了关于 Spatial Hashing 的那个页面并创建了我自己的实现,它现在工作得更好了。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-08
  • 1970-01-01
  • 2020-11-23
  • 1970-01-01
  • 2014-03-05
相关资源
最近更新 更多