Processing 3 improving intensive math calculation

那年仲夏 提交于 2019-12-23 02:22:28

问题


I wrote a very simple sketch to simulate the interference of two planar waves, very easy.

The problem seems to be a little to much intensive for the cpu (moreover processing uses only one core) and I get only 1 o 2 fps.

Any idea how to improve this sketch?

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:


As Kevin suggested, using point() isn't the most efficient method since it calls beginShape();vertex() and endShape();. You might be off better using pixels.

Additionally, the nested loops can be written as a single loop and dist() which uses square root behind the scenes can be avoided (you can uses squared distance with higher values).

Here's a version using these:

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");
}

This can be sped up further using lookup tables for the more time consuming functions such as sin() and exp().

You can see a rough (numbers need to be tweaked) preview running even in javascript:

var x1;
var y1;
var x2;
var y2;
var t = 0;

var factorA = 20*200;
var factorB = 80*200;

function setup() {
  createCanvas(400, 400);
  frameRate(30);
  x1 = width/4;
  y1 = height/2;
  x2 = width * 3/4;
  y2 = height / 2;
  loadPixels();
}

function draw() {
  for (var i = 0; i < pixels.length; i+= 4) {
    var x = i % width;
    var y = i / height;
    
    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);

    pixels[i] = pixels[i+1] = pixels[i+2] = (wave1 + wave2) * 255;
    pixels[i+3] = 255;
  }
  updatePixels();
  text(frameRate+"fps",10,15);
  //  endShape();
  t--; //Wave propagation
  //saveFrame("wave-##.png");
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.4.24/p5.min.js"></script>

Because you're using math to synthesise the image, it may make more sense to write this as a GLSL Shader. Be sure sure to checkout the PShader tutorial for more info.

Update:

Here's a GLSL version: code is less hacky and a lot more readable:

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);
}

And the 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);
}

You can use drag for first point and hold a key and drag for the second point. Additionally, use UP/DOWN, LEFT/RIGHT keys to change factorA and factorB. Results look interesting:

Also, you can grab a bit of code from this answer to save frames using Threads (I recommend saving uncompressed).




回答2:


Option 1: Pre-render your sketch.

This seems to be a static repeating pattern, so you can pre-render it by running the animation ahead of time and saving each frame to an image. I see that you already had a call to saveFrame() in there. Once you have the images saved, you can then load them into a new sketch and play them one frame at a time. It shouldn't require very many images, since it seems to repeat itself pretty quickly. Think of an animated gif that loops forever.

Option 2: Decrease the resolution of your sketch.

Do you really need pixel-perfect 400x400 resolution? Can you maybe draw to an image that's 100x100 and scale up?

Or you could decrease the resolution of your for loops by incrementing by more than 1:

  for (int x = 0; x <= width; x+=2) {
    for (int y = 0; y <= height; y+=2) {

You could play with how much you increase and then use the strokeWeight() or rect() function to draw larger pixels.

Option 3: Decrease the time resolution of your sketch.

Instead of moving by 1 pixel every 1 frame, what if you move by 5 pixels every 5 frames? Speed your animation up, but only move it every X frames, that way the overall speed appears to be the same. You can use the modulo operator along with the frameCount variable to only do something every X frames. Note that you'd still want to keep the overall framerate of your sketch to 30 or 60, but you'd only change the animation every X frames.

Option 4: Simplify your animation.

Do you really need to calculate every single pixels? If all you want to show is a series of circles that increase in size, there are much easier ways to do that. Calling the ellipse() function is much faster than calling the point() function a bunch of times. You can use other functions to create the blurry effect without calling point() half a million times every second (which is how often you're trying to call it).

Option 5: Refactor your code.

If all else fails, then you're going to have to refactor your code. Most of your program's time is being spent in the point() function- you can prove this by drawing an ellipse at mouseX, mouseY at the end of the draw() function and comparing the performance of that when you comment out the call to point() inside your nested for loops.

Computers aren't magic, so calling the point() function half a million times every second isn't free. You're going to have to decrease that number somehow, either by taking one (or more than one) of the above options, or by refactoring your code in some other way.

How you do that really depends on your actual goals, which you haven't stated. If you're just trying to render this animation, then pre-rendering it will work fine. If you need to have user interaction with it, then maybe something like decreasing the resolution will work. You're going to have to sacrifice something, and it's really up to you what that is.



来源:https://stackoverflow.com/questions/36883952/processing-3-improving-intensive-math-calculation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!