【问题标题】:Improving halftone / particles performance in P5js提高 P5js 中的半色调/粒子性能
【发布时间】:2026-01-31 12:15:01
【问题描述】:

我希望这些点能够平稳地反应,所以我想知道是否有办法提高这段代码的性能。

我正在尝试创建一个等距的点网格,既可以用作半色调效果(我已经达到),又可以用作对鼠标位置(重力/排斥)做出反应的粒子系统。

因为它应该表现得像半色调图像,所以点的密度应该保持相当高。 任何想法将不胜感激

let img;
let smallPoint, largePoint;
let res;
let manualBrightness = 6;
let lineLength = 1;
let row;
let gfg;

function preload() {
  img = loadImage('https://i.imgur.com/Jvh1OQm.jpg');
}

function setup() {
  createCanvas(400, 400);
  smallPoint = 4;
  largePoint = 40;
  imageMode(CENTER);
  noStroke();
  background(0);
  img.loadPixels();
  res = 5;

  row = 0;

  gfg = new Array(floor((img.height)/res));
  for (var i = 0; i < gfg.length; i++) {
    gfg[i] = new Array(floor((img.height)/res));
  }

  var h = 0;
  for (var i = 0; i < gfg.length; i++) {
    row++;
    let localI=i*res;
    for (var j = 0; j < gfg[0].length; j++) {
      let localJ = j*res*2*Math.sqrt(3);
      // localJ=localJ+res*2*Math.sqrt(3);
      gfg[i][j] = brightness(img.get(localJ, localI));
      // console.log(gfg[i][j]);
    }
  }
}

function draw() {
  background(0);

  row = 0;
  for (let i = 0; i<gfg.length; i++){
    let localI = i*res;
    row++;

    for (let j = 0; j<gfg[i].length; j++){
      let localJ = j*res*2*Math.sqrt(3);

      if(row%2==0){
        localJ=floor(localJ+res*Math.sqrt(3));

      }
      let pix = gfg[i][j];
      // B = brightness(pix);
      B=pix;
      B=(1/300)*B*manualBrightness;

      fill(255);
      stroke(255);
      strokeWeight(0);
      ellipse(localJ, localI,2,2);
      fill(255);
      let ellipseSize =B*res*(mouseX/width);
      // if(i%8==0 && (j+4)%8==0){
      //   ellipseSize = 4;
      // }
      ellipse(localJ, localI, ellipseSize,ellipseSize);
    }
  }
}
<html>
<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.2.0/p5.js" integrity="sha512-cuCpFhuSthtmbmQ5JjvU7msRYynRI67jVHsQhTP8RY+H4BC9qa9kQJeHTomV9/QnOWJbDpLFKdbIHtqTomJJug==" crossorigin="anonymous"></script>
</head>
<body>
<main>
</main>
</body>

【问题讨论】:

    标签: performance p5.js particles


    【解决方案1】:

    Tl;博士:web editor

    在尝试提高代码效率时,最好的办法之一就是在循环期间尽可能少地执行操作。在draw() 块中,您有一个嵌套的for 循环。这实际上是一种双嵌套循环,因为draw() 本身就是一个循环。这意味着在循环的最深层(当您迭代 j 时),您必须多次执行这些操作,并且必须在每一帧中执行。实际上可以将嵌套循环的最深部分减少到只有一个命令:在适当的位置和大小处绘制圆。

    我在这里提出的一些建议会使您的代码的可读性大大降低。这些建议仅适用于您需要更快地完成任务时,并且为了保持可读性,我建议放入 cmets。

    您的代码中的一些示例:

    • 对于每一个圆圈和每一帧,计算机都会设置两次填充颜色(两次都设置为相同的颜色),将 strokeWeight 设置为 0,将描边颜色设置为 255。这些都不是必需的。事实上,这些内容可以进入 setup() 块,因为它对于每一帧都是相同的。
    • 您在每个点和框架处绘制两个圆圈。如果第二个更大,则第一个是不可见的。您可能会惊讶于计算机在屏幕上绘制东西需要做多少工作。最好尽量减少:只画一个圆圈。您可以使用max() 设置大小,也可以使用circle() 代替ellipse()(我不知道使用circle() 是否真的更快,但对我来说看起来更好):
    circle(localJ, localI, max(2,ellipseSize));
    
    • 这让我想到了下一点:声明变量需要工作。不要使用过多的变量,并尝试在使用它们时准确定义它们。例如,在您的代码中,ellipseSize 是您插入到circle 函数中的变量,那么为什么不首先让它成为您想要的,而不定义 B 或 pix 呢? B 和 pix 都没有用来做其他事情,所以我们可以这样做:
    let ellipseSize = B*res*(mouseX/width);
    -> remove line 61: 
    let ellipseSize = (1/300)*B*manualBrightness*res*(mouseX/width);
    -> remove line 60:
    let ellipseSize = (1/300)*pix*manualBrightness*res*(mouseX/width);
    -> remove line 58:
    let ellipseSize = (1/300)*gfg[i][j]*manualBrightness*res*(mouseX/width);
    -> rearrange:
    let ellipseSize = gfg[i][j]*((manualBrightness*res)/(width*300))*mouseX;
    
    • 从这里开始,不需要每次都将gfg[i][j] 乘以(manualBrightness*res)/(width*300)。这些价值观永远不会改变。我们可以在这里做的是将所有这些内容移到 gfg 的定义中,第 37 行:
    gfg[i][j] = brightness(img.get(localJ, localI));
    ->
    gfg[i][j] = brightness(img.get(localJ, localI)) * (manualBrightness*res)/(width*300);
    let ellipseSize = gfg[i][j]*mouseX;
    
    • 现在,如果您查看 ellipseSize 的使用位置,您会发现它只在一个命令中使用过一次。这几乎不保证使用变量。我喜欢使用的一条规则是,如果您要使用它超过 3 次,则只创建一个变量。否则,它只会占用内存和时间。让我们看看是否还有其他可以摆脱的变量。

    row 在这个循环中做了什么?增加后,我们基本上只有row = i+1。另外,row 唯一的用途是检测奇偶校验,i 很容易做到这一点:

    row%2==0
    is the same as
    i%2 == 1
    

    所以没有必要在这个循环的任何地方出现row;我们可以使用i。 说到那个 if 语句,如果我们小心点,我们实际上可以摆脱它。 首先,我们可以去掉其中的 floor 函数,它并没有真正帮助任何事情。 现在让我们想一想……当i%2 为1(而不是0)时,我们将添加一些额外的东西。和说的一模一样

    localJ = localJ + (i%2)*res*Math.sqrt(3);
    

    没有必要的 if 语句。但是如果我们去掉 if 语句,那么我们将在一行中有两行为localJ 赋值。我们可以压缩这两行:

    let localJ = j*res*2*Math.sqrt(3);
    if(i%2==1){
      localJ = floor(localJ+res*Math.sqrt(3));
    }
    -> get rid of if statement
    let localJ = j*res*2*Math.sqrt(3);
    localJ = localJ+(i%2)*res*Math.sqrt(3);
    -> combine these two lines
    let localJ = j*res*2*Math.sqrt(3)+(i%2)*res*Math.sqrt(3);
    -> factor out res*Math.sqrt(3)
    let localJ = (2*j+(i%2))*res*Math.sqrt(3);
    

    但是现在我们有两个变量在一个命令中只使用一次。这不保证使用变量:

    let localJ = (2*j+(i%2))*res*Math.sqrt(3);
    let ellipseSize = gfg[i][j]*mouseX;
    circle(localJ, localI, max(2, ellipseSize));
    -> just put the formulas into circle()
    circle((2*j+(i%2))*res*Math.sqrt(3), 
           localI, 
           max(2, gfg[i][j]*mouseX)
          );
    

    现在我们已经做到了,嵌套循环的最深部分只有一个命令。但我们可以做得更好!平方根函数是最难的基本算术函数之一,我们在这里一遍又一遍地做它。所以让我们做一个变量!

    let sqrtShortcut;
    ...
    sqrtShortcut = res * Math.sqrt(3);
    ...
    circle((2*j+(i%2))*sqrtShortcut, 
           localI, 
           max(2, gfg[i][j]*mouseX)
          );
    

    我对@9​​87654352@ 遵循了相同的过程。我们还可以在这里做一件事,但不太明显。在 JavaScript 中,有一个名为 .map() 的数组方法。它基本上将一个函数应用于数组的每个元素,并返回一个具有更改值的新数组。我不会介绍应用程序,但它在下面的草图中。

    此时,真的没什么可做的了,但是我摆脱了一些未使用的变量和无用的命令。最后,它的运行速度比以前快了大约 5 倍。网页编辑器是here

    【讨论】:

    • 这真是太棒了。非常感谢!!!!