处理3改进密集数学计算
Processing 3 improving intensive math calculation
我写了一个很简单的草图来模拟两个平面波的干涉,很容易。
对于cpu来说,这个问题似乎有点严重(而且处理只使用一个核心),我只能得到 1 o 2 fps。
知道如何改进这张草图吗?
float x0;
float y0;
float x1;
float y1;
float x2;
float y2;
int t = 0;
void setup() {
//noLoop();
frameRate(30);
size(400, 400, P2D);
x0 = width/2;
y0 = height/2;
x1 = width/4;
y1 = height/2;
x2 = width * 3/4;
y2 = height / 2;
}
void draw() {
background(0);
for (int x = 0; x <= width; x++) {
for (int y = 0; y <= height; y++) {
float d1 = dist(x1, y1, x, y);
float d2 = dist(x2, y2, x, y);
float factorA = 20;
float factorB = 80;
float wave1 = (1 + (sin(TWO_PI * d1/factorA + t)))/2 * exp(-d1/factorB);
float wave2 = (1 + (sin(TWO_PI * d2/factorA + t)))/2 * exp(-d2/factorB);
stroke( (wave1 + wave2) *255);
point(x, y);
}
}
t--; //Wave propagation
//saveFrame("wave-##.png");
}
选项 1: 预渲染草图。
这似乎是一个静态的重复模式,因此您可以通过 运行 动画提前预渲染并将每一帧保存到图像中。我看到您已经在其中拨打了 saveFrame()
。保存图像后,您可以将它们加载到新草图中并一次播放一帧。它不需要太多图像,因为它似乎很快就会自我重复。想一想永远循环播放的动画 gif。
选项 2:降低草图的分辨率。
您真的需要像素完美的 400x400 分辨率吗?你可以绘制一张 100x100 的图像并按比例放大吗?
或者您可以通过增加 1 以上来降低 for 循环的分辨率:
for (int x = 0; x <= width; x+=2) {
for (int y = 0; y <= height; y+=2) {
您可以尝试增加多少,然后使用 strokeWeight()
或 rect()
函数绘制更大的像素。
选项 3:降低草图的时间分辨率。
不是每 1 帧移动 1 个像素,而是每 5 帧移动 5 个像素会怎样?加快动画速度,但仅每 X 帧移动一次,这样整体速度看起来是一样的。您可以使用 modulo operator along with the frameCount 变量仅每隔 X 帧执行一些操作。请注意,您仍然希望将草图的整体帧速率保持在 30 或 60,但您只会每隔 X 帧更改一次动画。
选项 4: 简化动画。
你真的需要计算每个像素吗?如果您只想显示一系列逐渐变大的圆圈,那么有更简单的方法可以做到这一点。调用 ellipse()
函数比多次调用 point()
函数要快得多。您可以使用其他函数来创建模糊效果,而无需每秒调用 point()
半百万次(这是您尝试调用它的频率)。
选项 5: 重构您的代码。
如果所有其他方法都失败了,那么您将不得不重构您的代码。你的程序的大部分时间都花在了 point()
函数上——你可以通过在 draw()
函数末尾的 mouseX, mouseY
处画一个椭圆来证明这一点,然后比较它的性能,当你在嵌套的 for 循环中注释掉对 point()
的调用。
计算机不是魔法,因此每秒调用 point()
函数一百万次并不是免费的。您将不得不以某种方式减少该数字,方法是采用上述选项中的一个(或多个),或者以其他方式重构您的代码。
你如何做到这一点实际上取决于你没有说明的实际目标。如果你只是想渲染这个动画,那么预渲染就可以了。如果您需要与用户进行交互,那么降低分辨率之类的方法可能会起作用。你将不得不牺牲一些东西,这真的取决于你。
正如 Kevin 所建议的那样,使用 point()
并不是最有效的方法,因为它会调用 beginShape();vertex() and endShape();
。使用像素可能会更好。
此外,嵌套循环可以写成单个循环,并且可以避免在幕后使用平方根的dist()
(您可以使用具有更高值的平方距离)。
这是一个使用这些的版本:
float x1;
float y1;
float x2;
float y2;
int t = 0;
//using larger factors to use squared distance bellow instead of dist(),sqrt()
float factorA = 20*200;
float factorB = 80*200;
void setup() {
//noLoop();
frameRate(30);
size(400, 400);
x1 = width/4;
y1 = height/2;
x2 = width * 3/4;
y2 = height / 2;
//use pixels, not points()
loadPixels();
}
void draw() {
for (int i = 0; i < pixels.length; i++) {
int x = i % width;
int y = i / height;
float dx1 = x1-x;
float dy1 = y1-y;
float dx2 = x2-x;
float dy2 = y2-y;
//squared distance
float d1 = dx1*dx1+dy1*dy1;//dist(x1, y1, x, y);
float d2 = dx2*dx2+dy2*dy2;//dist(x2, y2, x, y);
float wave1 = (1 + (sin(TWO_PI * d1/factorA + t))) * 0.5 * exp(-d1/factorB);
float wave2 = (1 + (sin(TWO_PI * d2/factorA + t))) * 0.5 * exp(-d2/factorB);
pixels[i] = color((wave1 + wave2) *255);
}
updatePixels();
text((int)frameRate+"fps",10,15);
// endShape();
t--; //Wave propagation
//saveFrame("wave-##.png");
}
对于更耗时的函数,例如 sin()
和 exp()
。
,可以使用查找表进一步加快速度
即使在 javascript:
中也可以看到粗略的(数字需要调整)预览 运行
var x1;
var y1;
var x2;
var y2;
var t = 0;
var factorA = 20*200;
var factorB = 80*200;
var numPixels;
var scaledWidth;
function setup() {
createCanvas(400, 400);
fill(255);
frameRate(30);
x1 = width /4;
y1 = height /2;
x2 = width * 3/4;
y2 = height / 2;
loadPixels();
numPixels = (width * height) * pixelDensity();
scaledWidth = width * pixelDensity();
}
function draw() {
for (var i = 0, j = 0; i < numPixels; i++, j += 4) {
var x = i % scaledWidth;
var y = floor(i / scaledWidth);
var dx1 = x1 - x;
var dy1 = y1 - y;
var dx2 = x2 - x;
var dy2 = y2 - y;
var d1 = (dx1 * dx1) + (dy1 * dy1);//dist(x1, y1, x, y);
var d2 = (dx2 * dx2) + (dy2 * dy2);//dist(x2, y2, x, y);
var wave1 = (1 + (sin(TWO_PI * d1 / factorA + t))) * 0.5 * exp(-d1 / factorB);
var wave2 = (1 + (sin(TWO_PI * d2 / factorA + t))) * 0.5 * exp(-d2 / factorB);
var gray = (wave1 + wave2) * 255;
pixels[j] = pixels[j+1] = pixels[j+2] = gray;
pixels[j+3] = 255;
}
updatePixels();
text(frameRate().toFixed(2)+"fps",10,15);
t--; //Wave propagation
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
因为您正在使用数学来合成图像,所以将其编写为 GLSL 着色器可能更有意义。请务必查看 PShader tutorial 了解更多信息。
更新:
这是一个 GLSL 版本:代码更简洁,可读性更高:
float t = 0;
float factorA = 0.20;
float factorB = 0.80;
PShader waves;
void setup() {
size(400, 400, P2D);
noStroke();
waves = loadShader("waves.glsl");
waves.set("resolution", float(width), float(height));
waves.set("factorA",factorA);
waves.set("factorB",factorB);
waves.set("pt1",-0.5,0.0);
waves.set("pt2",0.75,0.0);
}
void draw() {
t++;
waves.set("t",t);
shader(waves);
rect(0, 0, width, height);
}
void mouseDragged(){
float x = map(mouseX,0,width,-1.0,1.0);
float y = map(mouseY,0,height,1.0,-1.0);
println(x,y);
if(keyPressed) waves.set("pt2",x,y);
else waves.set("pt1",x,y);
}
void keyPressed(){
float amount = 0.05;
if(keyCode == UP) factorA += amount;
if(keyCode == DOWN) factorA -= amount;
if(keyCode == LEFT) factorB -= amount;
if(keyCode == RIGHT) factorB += amount;
waves.set("factorA",factorA);
waves.set("factorB",factorB);
println(factorA,factorB);
}
和 waves.glsl:
#define PROCESSING_COLOR_SHADER
uniform vec2 pt1;
uniform vec2 pt2;
uniform float t;
uniform float factorA;
uniform float factorB;
const float TWO_PI = 6.283185307179586;
uniform vec2 resolution;
uniform float time;
void main(void) {
vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
float d1 = distance(pt1,p);
float d2 = distance(pt2,p);
float wave1 = (1.0 + (sin(TWO_PI * d1/factorA + t))) * 0.5 * exp(-d1/factorB);
float wave2 = (1.0 + (sin(TWO_PI * d2/factorA + t))) * 0.5 * exp(-d2/factorB);
float gray = wave1 + wave2;
gl_FragColor=vec4(gray,gray,gray,1.0);
}
您可以拖动第一个点,按住一个键拖动第二个点。
此外,使用 UP/DOWN
、LEFT/RIGHT
键更改 factorA 和 factorB。结果看起来很有趣:
此外,您可以从 this answer 中获取一些代码以使用 Threads 保存帧(我建议保存未压缩的)。
我写了一个很简单的草图来模拟两个平面波的干涉,很容易。
对于cpu来说,这个问题似乎有点严重(而且处理只使用一个核心),我只能得到 1 o 2 fps。
知道如何改进这张草图吗?
float x0;
float y0;
float x1;
float y1;
float x2;
float y2;
int t = 0;
void setup() {
//noLoop();
frameRate(30);
size(400, 400, P2D);
x0 = width/2;
y0 = height/2;
x1 = width/4;
y1 = height/2;
x2 = width * 3/4;
y2 = height / 2;
}
void draw() {
background(0);
for (int x = 0; x <= width; x++) {
for (int y = 0; y <= height; y++) {
float d1 = dist(x1, y1, x, y);
float d2 = dist(x2, y2, x, y);
float factorA = 20;
float factorB = 80;
float wave1 = (1 + (sin(TWO_PI * d1/factorA + t)))/2 * exp(-d1/factorB);
float wave2 = (1 + (sin(TWO_PI * d2/factorA + t)))/2 * exp(-d2/factorB);
stroke( (wave1 + wave2) *255);
point(x, y);
}
}
t--; //Wave propagation
//saveFrame("wave-##.png");
}
选项 1: 预渲染草图。
这似乎是一个静态的重复模式,因此您可以通过 运行 动画提前预渲染并将每一帧保存到图像中。我看到您已经在其中拨打了 saveFrame()
。保存图像后,您可以将它们加载到新草图中并一次播放一帧。它不需要太多图像,因为它似乎很快就会自我重复。想一想永远循环播放的动画 gif。
选项 2:降低草图的分辨率。
您真的需要像素完美的 400x400 分辨率吗?你可以绘制一张 100x100 的图像并按比例放大吗?
或者您可以通过增加 1 以上来降低 for 循环的分辨率:
for (int x = 0; x <= width; x+=2) {
for (int y = 0; y <= height; y+=2) {
您可以尝试增加多少,然后使用 strokeWeight()
或 rect()
函数绘制更大的像素。
选项 3:降低草图的时间分辨率。
不是每 1 帧移动 1 个像素,而是每 5 帧移动 5 个像素会怎样?加快动画速度,但仅每 X 帧移动一次,这样整体速度看起来是一样的。您可以使用 modulo operator along with the frameCount 变量仅每隔 X 帧执行一些操作。请注意,您仍然希望将草图的整体帧速率保持在 30 或 60,但您只会每隔 X 帧更改一次动画。
选项 4: 简化动画。
你真的需要计算每个像素吗?如果您只想显示一系列逐渐变大的圆圈,那么有更简单的方法可以做到这一点。调用 ellipse()
函数比多次调用 point()
函数要快得多。您可以使用其他函数来创建模糊效果,而无需每秒调用 point()
半百万次(这是您尝试调用它的频率)。
选项 5: 重构您的代码。
如果所有其他方法都失败了,那么您将不得不重构您的代码。你的程序的大部分时间都花在了 point()
函数上——你可以通过在 draw()
函数末尾的 mouseX, mouseY
处画一个椭圆来证明这一点,然后比较它的性能,当你在嵌套的 for 循环中注释掉对 point()
的调用。
计算机不是魔法,因此每秒调用 point()
函数一百万次并不是免费的。您将不得不以某种方式减少该数字,方法是采用上述选项中的一个(或多个),或者以其他方式重构您的代码。
你如何做到这一点实际上取决于你没有说明的实际目标。如果你只是想渲染这个动画,那么预渲染就可以了。如果您需要与用户进行交互,那么降低分辨率之类的方法可能会起作用。你将不得不牺牲一些东西,这真的取决于你。
正如 Kevin 所建议的那样,使用 point()
并不是最有效的方法,因为它会调用 beginShape();vertex() and endShape();
。使用像素可能会更好。
此外,嵌套循环可以写成单个循环,并且可以避免在幕后使用平方根的dist()
(您可以使用具有更高值的平方距离)。
这是一个使用这些的版本:
float x1;
float y1;
float x2;
float y2;
int t = 0;
//using larger factors to use squared distance bellow instead of dist(),sqrt()
float factorA = 20*200;
float factorB = 80*200;
void setup() {
//noLoop();
frameRate(30);
size(400, 400);
x1 = width/4;
y1 = height/2;
x2 = width * 3/4;
y2 = height / 2;
//use pixels, not points()
loadPixels();
}
void draw() {
for (int i = 0; i < pixels.length; i++) {
int x = i % width;
int y = i / height;
float dx1 = x1-x;
float dy1 = y1-y;
float dx2 = x2-x;
float dy2 = y2-y;
//squared distance
float d1 = dx1*dx1+dy1*dy1;//dist(x1, y1, x, y);
float d2 = dx2*dx2+dy2*dy2;//dist(x2, y2, x, y);
float wave1 = (1 + (sin(TWO_PI * d1/factorA + t))) * 0.5 * exp(-d1/factorB);
float wave2 = (1 + (sin(TWO_PI * d2/factorA + t))) * 0.5 * exp(-d2/factorB);
pixels[i] = color((wave1 + wave2) *255);
}
updatePixels();
text((int)frameRate+"fps",10,15);
// endShape();
t--; //Wave propagation
//saveFrame("wave-##.png");
}
对于更耗时的函数,例如 sin()
和 exp()
。
即使在 javascript:
中也可以看到粗略的(数字需要调整)预览 运行var x1;
var y1;
var x2;
var y2;
var t = 0;
var factorA = 20*200;
var factorB = 80*200;
var numPixels;
var scaledWidth;
function setup() {
createCanvas(400, 400);
fill(255);
frameRate(30);
x1 = width /4;
y1 = height /2;
x2 = width * 3/4;
y2 = height / 2;
loadPixels();
numPixels = (width * height) * pixelDensity();
scaledWidth = width * pixelDensity();
}
function draw() {
for (var i = 0, j = 0; i < numPixels; i++, j += 4) {
var x = i % scaledWidth;
var y = floor(i / scaledWidth);
var dx1 = x1 - x;
var dy1 = y1 - y;
var dx2 = x2 - x;
var dy2 = y2 - y;
var d1 = (dx1 * dx1) + (dy1 * dy1);//dist(x1, y1, x, y);
var d2 = (dx2 * dx2) + (dy2 * dy2);//dist(x2, y2, x, y);
var wave1 = (1 + (sin(TWO_PI * d1 / factorA + t))) * 0.5 * exp(-d1 / factorB);
var wave2 = (1 + (sin(TWO_PI * d2 / factorA + t))) * 0.5 * exp(-d2 / factorB);
var gray = (wave1 + wave2) * 255;
pixels[j] = pixels[j+1] = pixels[j+2] = gray;
pixels[j+3] = 255;
}
updatePixels();
text(frameRate().toFixed(2)+"fps",10,15);
t--; //Wave propagation
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
因为您正在使用数学来合成图像,所以将其编写为 GLSL 着色器可能更有意义。请务必查看 PShader tutorial 了解更多信息。
更新:
这是一个 GLSL 版本:代码更简洁,可读性更高:
float t = 0;
float factorA = 0.20;
float factorB = 0.80;
PShader waves;
void setup() {
size(400, 400, P2D);
noStroke();
waves = loadShader("waves.glsl");
waves.set("resolution", float(width), float(height));
waves.set("factorA",factorA);
waves.set("factorB",factorB);
waves.set("pt1",-0.5,0.0);
waves.set("pt2",0.75,0.0);
}
void draw() {
t++;
waves.set("t",t);
shader(waves);
rect(0, 0, width, height);
}
void mouseDragged(){
float x = map(mouseX,0,width,-1.0,1.0);
float y = map(mouseY,0,height,1.0,-1.0);
println(x,y);
if(keyPressed) waves.set("pt2",x,y);
else waves.set("pt1",x,y);
}
void keyPressed(){
float amount = 0.05;
if(keyCode == UP) factorA += amount;
if(keyCode == DOWN) factorA -= amount;
if(keyCode == LEFT) factorB -= amount;
if(keyCode == RIGHT) factorB += amount;
waves.set("factorA",factorA);
waves.set("factorB",factorB);
println(factorA,factorB);
}
和 waves.glsl:
#define PROCESSING_COLOR_SHADER
uniform vec2 pt1;
uniform vec2 pt2;
uniform float t;
uniform float factorA;
uniform float factorB;
const float TWO_PI = 6.283185307179586;
uniform vec2 resolution;
uniform float time;
void main(void) {
vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
float d1 = distance(pt1,p);
float d2 = distance(pt2,p);
float wave1 = (1.0 + (sin(TWO_PI * d1/factorA + t))) * 0.5 * exp(-d1/factorB);
float wave2 = (1.0 + (sin(TWO_PI * d2/factorA + t))) * 0.5 * exp(-d2/factorB);
float gray = wave1 + wave2;
gl_FragColor=vec4(gray,gray,gray,1.0);
}
您可以拖动第一个点,按住一个键拖动第二个点。
此外,使用 UP/DOWN
、LEFT/RIGHT
键更改 factorA 和 factorB。结果看起来很有趣:
此外,您可以从 this answer 中获取一些代码以使用 Threads 保存帧(我建议保存未压缩的)。