【问题标题】:Drawing lots of particles efficiently有效地绘制大量粒子
【发布时间】:2012-11-11 08:43:05
【问题描述】:

我写了一个粒子系统小程序;目前我正在创建并分别绘制每个粒子。 (这里是代码)

BufferedImage backbuffer;
Graphics2D g2d;

public void init(){
    backbuffer = new BufferedImage(WIDTH,HEIGHT,BufferedImage.TYPE_INT_RGB);
    g2d = backbuffer.createGraphics();
    setSize(WIDTH, HEIGHT);

    //creates the particles
    for (int i = 0; i < AMOUNTPARTICLES; i++) {
        prtl[i] = new particleO();
        prtl[i].setX(rand.nextInt(STARTX));
        prtl[i].setY(rand.nextInt(STARTY));
        prtl[i].setVel(rand.nextInt(MAXSPEED)+1);
        prtl[i].setFAngle(Math.toRadians(rand.nextInt(ANGLESPREAD)));

        }

    //other code
}



    public void update(Graphics g) {        

    g2d.setTransform(identity);

    //set background
    g2d.setPaint(BGCOLOUR);
    g2d.fillRect(0,0,getSize().width,getSize().height);
    drawp();
    paint(g);           
    }


public void drawp() {

    for (int n = 0; n < AMOUNTPARTICLES; n++) {

    if (prtl[n].getAlive()==true){
            g2d.setTransform(identity);
            g2d.translate(prtl[n].getX(), prtl[n].getY());
            g2d.setColor(prtl[n].getColor());

            g2d.fill(prtl[n].getShape());


            }
    }

}

它的性能还不错,我可以在 20,000 个粒子的情况下获得约 40FPS(虽然,我有一台不错的笔记本电脑)。但是在我添加了碰撞检测(见下文)之后,这个数字骤降到不到 2000 个,

public void particleUpdate(){
 for (int i = 0; i < AMOUNTPARTICLES; i++) {
        //other update code (posx, posy, angle etc etc)

          for (int j = 0; j < AMOUNTPARTICLES; j++) {

                if (i!=j && prtl[j].getAlive()==true){

                     if(hasCollided(i, j)){
                        prtl[i].setcolor(Color.BLACK);
                        prtl[j].setcolor(Color.BLACK);
     }
            }
  }

public boolean hasCollided(int prt1, int prt2){

        double dx = prtl[prt1].getX() - prtl[prt2].getX();
        double dy = prtl[prt1].getY() - prtl[prt2].getY();
        int edges =  prtl[prt1].getRadius() + prtl[prt2].getRadius();

        double distance = Math.sqrt( (dx*dx) + (dy*dy) );
        return (distance <= edges);


    }

我已经搜索了很多将粒子绘制到屏幕上的更好方法,但这些示例要么让我感到困惑,要么不适用。

我正在做大量的计算(太多)。但我想不出另一种方法,欢迎提出建议。

【问题讨论】:

标签: java performance algorithm graphics2d


【解决方案1】:

首先,添加诸如碰撞检测之类的东西总是会占用大量内存。不过,让我们看看你的碰撞检测算法

public void particleUpdate(){
 for (int i = 0; i < AMOUNTPARTICLES; i++) {
        //other update code (posx, posy, angle etc etc)

          for (int j = 0; j < AMOUNTPARTICLES; j++) {

                if (i!=j && prtl[j].getAlive()==true){

                     if(hasCollided(i, j)){
                        prtl[i].setcolor(Color.BLACK);
                        prtl[j].setcolor(Color.BLACK);
                }
            }
  }

假设只有 2 个粒子,1 和 2。您将按顺序检查 1,1 1,2 2,1 2,2

事实上,在这种情况下,您只需要检查 1 与 2。如果 1 命中 2,则 2 也将命中 1。因此,更改之前测试过的 for 循环跳过,并为此使用相同的数字。

public void particleUpdate(){
 for (int i = 0; i < AMOUNTPARTICLES; i++) {
        //other update code (posx, posy, angle etc etc)

          for (int j = i+1; j < AMOUNTPARTICLES; j++) {

我注意到的另一件事是您执行了sqrt 操作,但只是为了与看起来像静态数字的内容进行比较。如果你去掉它,并将它与平方数进行比较,你会得到很大的改进,尤其是在你做了很多事情的时候。

    double distance_squared = ( (dx*dx) + (dy*dy) );
    return (distance <= edges*edges);

继续寻找这样的改进。然后您可能会仔细查看其他选项,例如使用不同的类、线程等,这些都可能会改进系统。但请确保首先优化代码。以下是我会尝试的其他事情的列表,大致按顺序排列。

  1. 在 i 出现后,在计算任何其他内容之前检查粒子 i 是否还活着。
  2. 快速通过对,如果它们接近,甚至会费心仔细检查。一种简单的方法是先检测它们是否在 x 和 y 维度内,然后再进行 sqrt 操作。始终先进行最便宜的测试,然后再进行复杂的测试。
  3. 查看您的代码,看看您是否真的使用了所有计算出的值,或者您是否能够以某种方式减少操作次数。
  4. 也许您可以使用粗笔划定期对图像进行聚类,然后仅在一段时间内细化通过初始聚类的对象,然后执行广泛的聚类算法。
  5. 您可以线程化碰撞检测。但是,如果您打算这样做,您应该只执行线程检查以查看是否有碰撞,并且在所有这些线程完成后,更新视图上的对象。
  6. 研究替代架构,这可能会加快一些速度。

【讨论】:

  • 非常感谢,我正在考虑类似的东西(嵌套的 for),但它不会从我的脑海中浮现(+1'ed)
  • 没问题。优化这样的东西可能是我最喜欢的编程部分:-)
  • 另外,如何检查粒子是否移动了?仅绘制自上一帧以来移动过的粒子。碰撞也是如此。 2 个未移动的粒子不会相互碰撞。
  • 这是可以做到的,但它本质上是通过细分图像来包含的。这取决于粒子不移动的频率。
【解决方案2】:

您正在检查所有粒子与所有粒子的碰撞,这是一个相当大的要求,大约为 n^2(2,000 个粒子意味着每帧有 4,000,000 个组合)。

问题不是java,而是算法。必须有更好的选择,但首先可以减少比较;如果您有最大速度 S 并且您的世界中的时间以 T 递增,则使用 T*S 您可以获得可以与您正在考虑的粒子碰撞的粒子的最大距离 D。将搜索减少到距离等于或小于该距离的那些粒子。也许将搜索限制在以您的粒子为中心且高度/宽度为 D 的正方形中会更容易(这将包括一些太远的粒子,但会使检查更容易)。

此外,在您的代码中,您正在检查 P1 与 P2 和 P2 与 P1 的冲突,这是相同的,这是您可以轻松避免的冗余。

【讨论】:

    【解决方案3】:

    绘画是一个复杂的过程,绘画请求可能因多种原因触发,操作系统可能希望更新窗口,重新绘画管理器可能希望重新绘画,程序员可能希望重新绘画。

    paint 进程中更新粒子是个坏主意。您要做的是在单独缓冲区的单独线程中更新粒子。准备好后,请求负责绘制缓冲区的组件执行重新绘制,将缓冲区的新副本传递给重新绘制(您不想在开始更新到屏幕的缓冲区上进行绘制,您最终会弄脏油漆)。

    很难从你的代码中分辨出来,但看起来你正在使用java.awt.Applet,就个人而言,我会升级到javax.swing.JApplet

    我会将这幅画移至java.swing.JPanel。 Swing 组件提供双重缓冲(以及其他缓冲策略)。此面板的唯一工作是在粒子引擎有新帧时在屏幕上绘制缓冲区。

    粒子引擎负责更新所有粒子并将这些结果绘制到后备缓冲区 (BufferedImage),然后将其交给面板,面板将制作副本并安排更新。

    Swing 不是线程安全的。也就是说,您不应该从除 Event Dispatching Thread 之外的任何线程对 UI 进行更改。为此,您可能希望阅读Concurrency in Swing 以了解将屏幕外缓冲区重新同步到客户端的解决方案。

    【讨论】:

      猜你喜欢
      • 2023-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-09
      • 2011-09-15
      • 2012-03-31
      • 2017-03-22
      • 1970-01-01
      相关资源
      最近更新 更多