Simulate tree shadow?

Hi, I’ve been using p5js and processing for a while now and have some experience making animations/art using sine waves, noise, etc. but I’m currently wondering how to re-create some art that I’ve found and have been a bit stumped. It involves simulating light passing through trees in a believable way.

There’s a video of what I’m going for. I’m trying to figure out how to simulate that and was wondering if anyone knew of any resources for something like that or similar. I was thinking maybe it uses something similar to a marching squares algorithm.
http://jamie-wong.com/2014/08/19/metaballs-and-marching-squares/
Or maybe it just fully renders a 3d scene with this but I was thinking there must be a way to “cheat” this effect in 2d. Thanks

1 Like

Looks like you took on quite a challenge. The thing with the meatballs and marching squares looks really cool and probably worth looking into and coding a version of it yourself; and I can definitely see how this video made you think it would help you. However, I think that while it might look similar, the underlying mechanisms are very different and don’t really have anything to do with each other.
The thing that makes the shadows (or rather the part where the sunlight comes through) look circular is because the sun isn’t a point light source. It takes up an angle in the sky, and so different light rays come from different angles, which makes almost all shadows created by the sun look blurred, instead of having clear, defined edges.
This can be simulated, though it is going to require quite a bit of processing power, so it might run pretty slow, depending on your setup.
As for getting the actual, non blurred out shadows, you can probably just use a bunch of 2d silhouettes to create the effect. You could actually go fully procedural for this one.
Let’s start by creating an equation that checks if a point p is inside or outside a leaf shape.

boolean insideLeaf(PVector p) {
  p.y = abs(p.y);
  return pow(p.x, 2)+pow(p.y+0.6, 2) < 1;  //sign(x) = x/abs(x)
}

It is the a modification for the equation of a circle (x^2+y^2=1).

Now, let’s create a Leaf class, giving each Leaf a position, scale and rotation.

class Leaf {
  PVector pos;
  float sc;
  float rot;
  
  Leaf (PVector p, float s, float r) {
    pos = new PVector(random(width), random(height));
    sc = random(50, 120);
    rot = random(TWO_PI);
  }
}

We can also give each Leaf a method for checking if any given point is inside or outside…

boolean pointInside(PVector p) {
  p.sub(pos);  //applying the inverse transformation to the point, thus making it as if the leaf had no transformation at all
  p.div(sc);
  p.rotate(-rot);
  return insideLeaf(p);
}

… as well as some primitive, subtle size and rotation changing method, which will be called once every frame

void changeScRot() {
  rot += random(-0.05, 0.05);
  sc *= random(0.99, 1.01);
}

We can then make an ArrayList of leaves.

ArrayList<Leaf> leaves;

void setup() {
  //size() and other stuff
  leaves = new ArrayList<Leaf> ();
  for (int a=0; a<20; a++) {
    leaves.add(new Leaf());
  }
}

Now, we just have to iterate through every pixel, check if it lies within any given leaf, and if it does, color it black. If not, we color it white.

void draw() {
  for (Leaf l: leaves) {
    l.changeScRot();
  }
  
  background(255);
  noStroke();
  for (int y=0; y<heigth; y++) {
    for (int x=0; x<width; x++) {
      fill(255);
      for (int b=0; b<leaves.size(); b++) {
        if (leaves.get(b).pointInside(new PVector(x, y))) {
          fill(0);
          b = leaves.size();
        }
      }
      rect(x, y, 1, 1);
    }
  }
}

That’s it for the leaf-drawing part. The way the leaves change their scale and rotation is rather primitive, for a final, artistic render you should probably improve that a little.

Now onto the actual blurry shadows.
We first load everything that’s on the screen right now into a color array, using it as a reference for what we draw now. After clearing the contents of the screen, we go through every pixel on screen, and set its color based off the color of the surrounding pixels in the array we stored before.
shadowblur
The new color at the pixel p is the average of all the colors inside the circle, which has a certain radius (I called that variable blurscale). So all we have to do is iterate vertically from -blurscale to blurscale, and horizontally from -blurscalesqrt(1-(yoff/blurscale)^2) to blurscalesqrt(1-(yoff/blurscale)^2). This mask gives a circle. In code, this looks like this:

loadPixels();
  color [] p = pixels;
  
  background(255);
  for (int y=0; y<height; y++) {
    for (int x=0; x<width; x++) {
      float sum = 0;
      int max = 0;
      for (float yoff=-blurscale; yoff<blurscale; yoff+=blurres) {
        float xmax = blurscale*sqrt(1-pow(yoff/blurscale, 2));
        for (float xoff=-xmax; xoff<xmax; xoff+=blurres) {
          int x0 = floor(x + xoff);
          int y0 = floor(y + yoff);
          if (x0 >= 0 && x0 < width && y0 >= 0 && y0 < height) {
            sum += red(p[y0*width+x0]);
            max ++;
          }
        }
      }
      sum /= max;
      sum *= 3;
      fill(sum);
      rect(x, y, 1, 1);
    }
  }

And that’s pretty much it!
If you have any further questions, just post them here on the thread or ask me directly on discord (WeinSim aka Carrot#3361)
Simon

1 Like

Wow, thanks for that write up! That looks like a lot of really good info. I know blurring usually takes a good bit of processing power and can be difficult to do at 60fps unless you’re writing some kind of shader or something. I think in the final product with the leds, there will be some blurring occurring anyways that may mean less blurring has to happen in code. But I think that’s a good concern. I’m mostly worried about getting a realistic look and feel. Thank you very much again for all the info. I’ll post back once I’ve gotten back to my computer and tried out some of your suggestions.

If you’re using processing, just use the BLUR filter :

https://processing.org/reference/filter_.html


void setup() {
  size(1280, 720, P2D);
}

void draw() {
  background(255);
  fill(0);
  noStroke();
  ellipse(mouseX, mouseY, 100, 100);
  filter(BLUR, 5);
}

Of course, this only works in P2D or P3D sketches, but this is using a native processing shader and it is real-time for most blurs.

Furthermore, although this is technically GPU accelerated, I think it is quite slow because it isn’t separated. Here is a separated bluring shader I made a few months ago (written in glsl, to be stored in your sketch folder) :

#ifdef GL_ES
precision highp float;
precision mediump int;
#endif
 
#define PROCESSING_TEXTURE_SHADER
 
uniform sampler2D texture;

uniform vec2 texOffset;
 
varying vec4 vertColor;
varying vec4 vertTexCoord;

uniform int diameter;
uniform bool vertical;

float pow_int(float x, int n){
	float res = 1.0;
	for(int i = 0; i < n; i++) res = res * x;
	return res;
}

float fast_exp(float x, int n){
	float res = x;
	for(int i = 0; i < n; i++) res = res * res;
	return res;
}

float normal(float x){
	int prec = 11;
	return fast_exp((1 - (x * x)/pow_int(2, prec)), prec);
}

void main() {
	vec4 color = vec4(0.0);
	float size = 0;
	
	int diameterReal = min(diameter, 100000);
	
	for(int x = 0; x < diameterReal; x++){
		float kernelPixel = normal(2.3 * 2.0 * ((float(x) - float(diameterReal)/2.0)/float(diameterReal)));
		size += kernelPixel;
		color += kernelPixel * texture(texture, vec2(vertTexCoord.x + (vertical ? 0 : ((x - diameterReal/2)* texOffset.x)), vertTexCoord.y + (vertical ? ((x - diameterReal/2)* texOffset.y) : 0)));
	}
	
    gl_FragColor = color/size;
}

And here is the same sketch as before that implements it :

PShader blur;

void setup() {
  size(1280, 720, P2D);
  blur = loadShader("blur.glsl");
  blur.set("diameter", 100);
}

void draw() {
  background(255);
  fill(0);
  noStroke();
  ellipse(mouseX, mouseY, 100, 100);
  
  blur.set("vertical", true);
  filter(blur);
  blur.set("vertical", false);
  filter(blur);
}

This last code is running smoothly at 30 FPS even with a 100 px blur :slight_smile:

1 Like

Thank you! I’ll give that a shot too. I think one advantage of doing this with LEDs is that there is some natural blur/soft edge already present. The other advantage is the low resolution. I don’t know for sure, but it looks like there’s maybe somewhere around 64 rows of LEDs in the video. And if I guessed maybe 64 columns per strip, that’s a pretty low resolution of 64x64 for a pretty large area. A lot easier processing-wise to calculate than compared to say 1920x1080.

1 Like

I think with a 64x64 image, there shouldn’t be any problems with fps. After all, it is only 0.2% the amount of pixels, so I guess with the exact same settings the algorithm should run 500 times faster.
I’d also be interested to see the final result