如何解决改进 P5js 中的半色调/粒子性能
我希望圆点反应流畅,所以我想知道是否有办法提高此代码的性能。
我正在尝试创建一个等距的点网格,它既可以作为半色调效果(我已经达到),也可以作为对鼠标位置(重力/排斥)做出反应的粒子系统。
因为它应该像半色调图像一样,所以点的密度应该保持相当高。 任何想法将不胜感激
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,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>
解决方法
Tl;博士:web editor
在尝试提高代码效率时,最好的做法之一是在循环期间尽可能少做。在 draw()
块中,您有一个嵌套的 for
循环。这实际上是一种双嵌套循环,因为 draw()
本身就是一个循环。这意味着在循环的最深层次(当您迭代 j
时),您必须在每一帧中多次执行这些操作。实际上可以将嵌套循环的最深部分减少到只有一个命令:在适当的位置和大小画圆。
我在这里提出的一些建议会使您的代码可读性大大降低。这些建议仅适用于您需要更快地做事的情况,为了保持可读性,我建议您添加评论。
代码中的一些示例:
- 对于每一个圆和每一帧,计算机将填充颜色设置两次(两次都设置为相同的颜色),将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)
。这些价值观永远不会改变。我们在这里可以做的是将所有内容移至第 37 行的 gfg 定义:
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 语句,那么我们将连续两行给 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,ellipseSize));
-> just put the formulas into circle()
circle((2*j+(i%2))*res*Math.sqrt(3),gfg[i][j]*mouseX)
);
现在我们已经做到了,嵌套循环的最深部分只有一个命令。但我们可以做得更好!平方根函数是最难的基本算术函数之一,我们在这里一遍又一遍地做。所以让我们创建一个变量!
let sqrtShortcut;
...
sqrtShortcut = res * Math.sqrt(3);
...
circle((2*j+(i%2))*sqrtShortcut,gfg[i][j]*mouseX)
);
我对 localI
执行了相同的过程。我们还可以在这里做一件事,但不太明显。在 JavaScript 中,有一个名为 .map()
的数组方法。它基本上将一个函数应用于数组的每个元素,并返回一个具有更改值的新数组。我不会详细介绍应用程序,但它在下面的草图中。
此时,真的没有什么可做的了,但我摆脱了一些未使用的变量和无用的命令。最后,它的运行速度比以前快了大约 5 倍。网络编辑器是 here。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。