3D Perlin noise in Processing

Hello!

I just finished the coding train’s coding challenge #11 : 3D generation with perlin noise on YouTube! I have a question. How would I make a similar image but with my own image of noise that is responsive to the grid in the same way? I’ve been taking screenshots of loading imagery and I would like to have a sculptural 3D grid which responds to my own image of noise rather than random.

Thank you for your help! :space_invader::melting_face::yellow_heart::sparkles:

Here is the code that I have at the moment without the image!

//VARIABLES
int cols,rows;
int scl = 8; //scale
int w = 1200;
int h = 900;

float[][] terrain;

//INITIALIZE
void setup(){
  size(1000,1000, P3D);  
  cols = w/ scl;  
  rows = h/ scl;
  terrain = new float [cols][rows];
  float yoff = 0;
  for (int y = 0; y < rows; y++) {
    float xoff = 0.2;
    beginShape(TRIANGLE_STRIP);
    for (int x = 0; x< cols; x++) {   
      terrain[x][y] = map(noise(xoff,yoff), 0, 1, -200, 50);
      xoff += 0.2;
    }
    yoff += 0.1;
  }
  
} 

void draw(){
  background(0);
  stroke(255);
  smooth();
  noFill(); 
  //rect(x*scl, y*scl, scl, scl); // grid!!
  
  translate(width/2, height/2);  //rotate perspective
  //rotateX(PI/3);  //rotate perspective
  translate(-width/2,-height/2); //perspective 
 
  
  frameRate(1);
  
  for (int y = 0; y < rows-1; y++) {
    beginShape(QUAD_STRIP); // OR beginShape(QUAD_STRIP), (TRIANGLE_STRIP)
    for (int x = 0; x< cols; x++) {
      vertex(x*scl, y*scl, terrain[x][y]);
      vertex(x*scl, (y+1)*scl, terrain[x][y+1]);
      
       }
         endShape();
    }
}
1 Like

Hi @asymmetric,

Would you please Format your code

Some time ago I made an example for another post which uses a grid reacting on the underlying noise value by sizing the cells depending on that noise value. Maybe it gives you an idea which helps you. Here

Cheers
— mnse

1 Like

Hello @asymmetric ,

Take a look at the code that you posted.

Please edit your post and format and post code as per here:
https://discourse.processing.org/faq#format-your-code

Take a look at this tutorial:

You can get information about each pixel in your image and use that to generate vertices.

Consider brightness.

:)

1 Like

Hi MNSE! Thank you! That is defiantly very cool and I will try this code today! But I would like to have the grid react to a static image of noise rather than random numbers. I love the aesthetic of the black and white grid in the Perlin noise tutorial, but I would like to use an image that reacts to the grid. Here are some images that describe what I am looking for more clearly.


Can I use an image to create a similar interaction?
img = loadImage("Jan24.jpg");

Hello @asymmetric,

Absolutely!

I merged your code with some snippets from the tutorial and voila!

To get you started:

  img = loadImage("avatar_cool.png");
  terrain = new float [img.width][img.height];
  img.loadPixels();
  for (int x = 0; x < img.width; x++) 
    {
    // Loop through every pixel row
    for (int y = 0; y < img.height; y++) 
      {
      // Use the formula to find the 1D location
      int loc = x + y * img.width;
      
      int bright = int(brightness(img.pixels[loc]));
      terrain[x][y] = bright;
      }
    }
img.updatePixels(); //Not needed here

avatar_cool

Result:

Have fun!

:)

1 Like

One more question. I am very new to Processing and I am studying/reading the handbook every day, but I don’t completely understand the structure of the code quite yet!

With that said, where would I plug the code that you sent me? I entered it under the void setup(); but I keep getting errors. If you would not mind uploading the full code that you used to get to that image, I would be immensely grateful! Thank you so much glv!!!

Hi @asymmetric ,

As this initialize the terrain once depending on a static image, it should be placed in setup(). Afterwards the values of the terrain can be used in draw() to apply it to your grid and display it, but as it is static the grid can also be pre-defined in setup().

Cheers
— mnse

1 Like

Hi mnse! I placed the code that @glv wrote under the setup(). I have also declared a variable of type PImage, but I am still getting the same image when I run the compiler? If you could expand on how to initialize the terrain and how to use the values of the terrain in draw() that would be very appreciated! :pray:

This is the code I have at the moment.

//VARIABLES
PImage img; // Declare a variable of type PImage
int cols,rows;
int scl = 8; //scale
int w = 1200;
int h = 900;

float[][] terrain;

//INITIALIZE
void setup(){
  size(1000,1000, P3D);  
  img = loadImage("Jan24.jpg");
  terrain = new float [img.width][img.height];
  img.loadPixels();
  for (int x = 0; x < img.width; x++) 
    {
    // Loop through every pixel row
    for (int y = 0; y < img.height; y++) 
      {
      // Use the formula to find the 1D location
      int loc = x + y * img.width;
      
      int bright = int(brightness(img.pixels[loc]));
      terrain[x][y] = bright;
      }
    }
  
  
  cols = w/ scl;  
  rows = h/ scl;
  terrain = new float [cols][rows];
  float yoff = 0;
  for (int y = 0; y < rows; y++) {
    float xoff = 0.2;
    beginShape(TRIANGLE_STRIP);
    for (int x = 0; x< cols; x++) {   
      terrain[x][y] = map(noise(xoff,yoff), 0, 1, -200, 50);
      xoff += 0.2;
    }
    yoff += 0.1;
  }
  
} 


//img.updatePixels(); //Not needed here

void draw(){
  background(0);
  stroke(255);
  smooth();
  noFill(); 
  //rect(x*scl, y*scl, scl, scl); // grid!!
  
  translate(width/2, height/2);  //rotate perspective
  //rotateX(PI/3);  //rotate perspective
  translate(-width/2,-height/2); //perspective 
 
  
  frameRate(1);
  
  for (int y = 0; y < rows-1; y++) {
    beginShape(QUAD_STRIP); // OR beginShape(QUAD_STRIP), (TRIANGLE_STRIP)
    for (int x = 0; x< cols; x++) {
      vertex(x*scl, y*scl, terrain[x][y]);
      vertex(x*scl, (y+1)*scl, terrain[x][y+1]);
      
       }
         endShape();
    }
}

Hi @asymmetric,

You are here overwriting the values again from above… :slight_smile:

Was quite busy with other work the last days. I’ll try to post a complete example for you tomorrow…

Cheers
---- mnse

Thank you, @mnse!! You’re the best! I’ll check back again! :star_struck: :heart_decoration:

Hello @asymmetric ,

I will not provide you with the full code; I wrote that code so I could offer assistance.

This is an achievable exercise and worth the effort on your part to work through and understand.

I did this myself before providing you with some direction, a “snippet” of code and references.

I encourage you to work through this and try to understand it; this time invested will pay off in the long run.

The Processing Forum Guidelines are here:

Processing website:

I do not want to take the opportunity away from you!

If you have specific questions please ask.

:)

1 Like

Hi @glv,

in principle I totally agree with you!
Learning to code is a long process that never ends and that only works if you work hard at it and learn from your mistakes, but also from other people.
However, regarding the latter, for many it is necessary to look at how others do it. First, to familiarize yourself with it, and second, to see if you understand the approach or not, and to question why someone implemented it the way they did. This also allows you, after you have studied it, to ask further questions and, in the best case, to develop your own and perhaps new approaches that will help you and, in turn, other people who learn from you.
However, since I also understand the purpose of the guildlines you carefully selected, I’m a little torn about posting my finished example solution here. :grin:

@asymmetric: Means, I think, I need some more time to came up with a final decission to post it or not … So, try first on your own to see if you get a result by @glv’s snippet and if you not came up with a solution for your own, let us know …

Cheers
— mnse

EDIT: removed example output from here as posted completely below …

2 Likes



Hi again,

Well, as the day is slowly coming to an end and I’ve had some thoughts about whether to post or not, I’ve come to the decision that I will post it after all.
There are many pros and cons that could be listed, but none of them weigh so heavily that there would be a right or wrong.
Reasons are …
My code is just one of many examples and is far from an all-encompassing implementation. There are many things that can be worked out in addition to this on the topic.
I’ve now added lots of comments to the code, to not offer this as a simple copy/paste solution, but rather that one can get an idea of what concrete was done here and most importantly why.
(took longer than to code it. :grin:)

If you disagree with my decision, you are free to flag the post. :wink:

Code+Image
// a class to representing a point on our grid.
// This should/would/could be done better by defining it as faces (of a triangle mesh).
// but would it keep as simple as possible 
class GridPoint {
  float x, y, v;
  public GridPoint(float px, float py, float pv) {
    x = px;  // x-pos of the grid point
    y = py;  // y-pos of the grid point
    v = pv;  // height of the grid point
  }
}

//helper variables
float halfWidth, halfHeight;
boolean mode = false;
// storage of out grid
int rows, cols;
GridPoint[][] grid;
// image used for texture and heightmap
PImage img;

void setup() {
  size(800, 800, P3D);
  halfWidth  = width/2.;
  halfHeight = height/2.;
  initGrid(10.);
  // use textureMode IMAGE as the grid is not normalized
  textureMode(IMAGE);
}

void initGrid(float scl) {
  // load the image, used for texture and heightmap.
  // usually more than one picture will be used. One for heightmap, one for normalmap and one with the texture.
  // for simplicity it has the same size than our grid to not make the code more math intensive.
  // to get this work our image is somehow a bit blurred (like noise is), otherwise the mesh isn't smooth enough
  img = loadImage("image.png");
  img.loadPixels();
  // initialize our gridpoints
  rows = floor(height/scl);
  cols = floor(width/scl);
  grid= new GridPoint[rows][cols];
  for (int y = 0; y < rows; y++) {
    for (int x = 0; x < cols; x++) {
      int sx = floor(x*scl);
      int sy = floor(y*scl);
      // set x and y coordinates (center origin) and the height of the current point.
      // height is the normalized brightness value [0..1] of the pixel color from the image.
      // Subtract 0.5 to align it to center [-0.5..0.5]
      // scaling it by 50, so the range is [50.0*(-0.5)..50.0*(0.5)]
      grid[y][x] = new GridPoint(-halfWidth+sx+scl/2, -halfHeight+sy+scl/2, 50.*(brightness(img.get(sx, sy))/255-0.5));
    }
  }
}

void draw() {
  background(0);
  // set the origin to the center of the screen, and move it 400px far away on z-axis
  translate(halfWidth, halfHeight, -400);

  // switch between mesh and texured mode
  if (frameCount % int(TAU*100.) == 0) {
    mode = !mode;
  }

  if (mode) {
    // if textured mode no strokes and a light from viewpoint to object
    noStroke();
    directionalLight(255, 255, 255, 0, 0, -1);
  } else {
    // if mesh mode only show strokes
    noFill();
    stroke(128);
    strokeWeight(0.5);
  }

  // apply the rotation after lighting, otherwhile the light gets also rotated, which we not want.
  // tipping 30degree back on X-axis
  rotateX(radians(30));
  // tipping left/right a bit per frame from -PI/4 - +PI/4 on Y-axis
  rotateY(sin(frameCount/100.)*QUARTER_PI);

  // build the surface/terrain by triangle strips to display it
  for (int y = 0; y < rows-1; y ++) {
    beginShape(TRIANGLE_STRIP);
    if (mode) {
      // on textured mode set the texture
      texture(img);
    }
    // common triangle strips
    for (int x = 0; x < cols; x++) {
      GridPoint ca = grid[y][x];
      GridPoint cb = grid[y+1][x];
      vertex(ca.x, ca.y, ca.v, ca.x+halfWidth, ca.y+halfHeight);
      if (mode) {
        // on textured mode we need to set the normal vector, so the light knows how to behave on hittng the surface
        // usually there are better calculation than this cheap hack
        // comment it out to see the difference if normals are messed up
        PVector na = new PVector(ca.x-cb.x, ca.y-cb.y, 0).sub(new PVector(ca.x-cb.x, ca.y-cb.y, 1)).normalize();
        normal(na.x, na.y, na.z);
      }
      vertex(cb.x, cb.y, cb.v, cb.x+halfWidth, cb.y+halfHeight);
      if (mode) {
        // on textured mode we need to set the normal vector, so the light knows how to behave on hittng the surface
        // usually there are better calculation than this cheap hack
        // comment it out to see the difference if normals are messed up
        PVector nb = new PVector(cb.x-ca.x, cb.y-ca.y, 0).sub(new PVector(cb.x-ca.x, cb.y-ca.y, 1)).normalize();
        normal(nb.x, nb.y, nb.z);
      }
    }
    endShape();
  }
}

Image used for code above [800x800]px

Result:
output

Cheers
— mnse

5 Likes
PImage img;
int cols, rows;
int scl = 26;
int w = 2800;
int h = 2600;

float ax, ay;


int videoScale =14;

int cols1, rows1;





//int sphereMy ;
void setup() {
  size(1200, 1000, OPENGL);
//  rectMode(CENTER);
String http = "http://";
  //img = loadImage( http + "www.tfguy44.com/MyIcon1.PNG");


img = loadImage( "ee.jpeg");
 cols = w / scl;
  rows = h/ scl;
//cols1 = width/videoScale;
//  rows1 = height/videoScale;
 rows1 = 222/videoScale;
 cols1 = 222/videoScale;
  texture(img);

}
 
void draw() {
  background(0);
 
  lights();
 
 translate(width/2, height/2, 0);
 
  scale(1.5);
  rotateY(map(mouseX, 0, width, -PI, PI));
  rotateX(-map(mouseY, 0, height, -PI, PI));
 
  //point(455, 434, 122);
  for (int u=0; u<1; u+=1) {
    sphereMy(20-u, 10-u, 111-u);
  }

 // stroke(255, 0, 0);
 // fill(255, 0, 0, 128);
// noStroke(); 
 noFill(); 
 stroke(255, 255, 255);
// fill(0, 0, 255, 128);
  plane();
 
 // stroke(0, 255, 0);
 // fill(0, 255, 0, 128);
  rotateX(HALF_PI);
 //noStroke(); 
// noFill(); 
stroke(255, 255, 255);
 //fill(0, 0, 255, 128);
  plane();
// NoStrok();
// noStroke(); 
// noFill(); 
 stroke(255, 255, 255);
//fill(255, 255, 255);
 rotateY(HALF_PI);
  plane();
}
 
void plane() {
  //for (int f=-6; f<7; f++) {
 //   for (int c=-6; c<7; c++) {
     // rect(10*c, 10*f, 10, 10);
   for (int k = 0; k < cols1; k++) {
    // Begin loop for rows
    for (int j = 0; j < rows1; j++) {

      // Scaling up to draw a rectangle at (x,y)
      int g = k*videoScale;
      int t = j*videoScale;
     // fill(255);
      //stroke(0);
      // For every column and row, a rectangle is drawn at an (x,y) location scaled and sized by videoScale.
      rect(g, t, videoScale, videoScale); 
  
  


}
  }
}
//void sphereMy(float s, float d, float w) {
 void sphereMy(float l, float r, float e) {
 lights();
  


  background(4);
  
   
   pushMatrix();
  noStroke(); 
 // fill(255, 111, 11);
  translate(r, l, e); 
  sphere (2); 
 // translate(width/2, height/2);
 // rotateY(map(mouseX, 0, width, -PI, PI));
  //rotateX(map(mouseY, 0, width, -PI, PI));
 scale(0.07);

  translate(-w/12, -h/12);
  for (int y = 0; y < rows-1; y++) {
   
    beginShape(TRIANGLE_STRIP);
     
    texture(img);


    for (int x = 0; x < cols; x++) {
      ax = map(x, 0, cols, 0, img.width);
      ay = map(y, 0, rows, 0, img.height);

      vertex(x*scl, y*scl, map(brightness(img.get(int(ax), int(ay))), 320, 255, 0, 200), ax, ay  );

      ax = map(x, 0, cols, 0, img.width);
      ay = map(y+1, 0, rows, 0, img.height);

      vertex(x*scl, (y+1)*scl, map(brightness(img.get(int(ax), int(ay))), 320, 255, 0, 200), ax, ay );
      //rect(x*scl, y*scl, scl, scl);
    }
    endShape();
  }
  
  
  
  
  popMatrix();
}


1 Like

Thank you, @mnse!! I really appreciate your help as well as the extra time you spent clarifying in the code comments. In the future, I would like to edit the actual grid into different shapes and varying geometric designs which will react in the same way to the underlying static image. With that being said, I will be studying the code that you sent me this week and trying to make sense of how you connected it all.

As a beginner, my first idea on how to connect the aesthetic of the Perlin noise grid to a static image was to combine the code of the tutorial with the example code: 2D image mapped to 3D. I am still having a disconnect between the two, but is it possible to also get similar results with the following code as well?

PImage img;

void setup(){
  size(900, 900, P3D);
  img = loadImage("Jan24.jpg");
  img.resize(900,900);
  
}

void draw() {
  background(#f1f1f1);
  fill(0);
  noStroke();
  sphereDetail(3);
  //ellipse(mouseX,mouseY,40,40);
  
  float tiles = 100;
  float tileSize = width/tiles;
  
  push();
  rotateY(radians(frameCount) );
  translate(width/2,height/2);
  
  for (int x = 0; x < tiles; x++) {
    for (int y = 0; y < tiles; y++) {
      
    color c = img.get(int(x*tileSize),int(y*tileSize));
    float b = map(brightness(c),0,255,01,0); 
    
    float z = map(b,0,1,-100,100);
    
    push();
    translate(x*tileSize - width/2,y*tileSize- height/2, z);
    sphere(tileSize*b);
    pop();
    
    
    }
    
  }
    
}

Thank you again! Your code is very helpful for me to bounce off and learn!! :heart_decoration: :star_struck:


Also, I played around with your code and edited it a bit! I’m so excited to keep editing and learning!

:star_struck:

Hi @jafal! I tried compiling your code but it doesn’t work for some reason? I’m interested in seeing the way you did it as well and learning more! Thank you so much!

1 Like

@asymmetric

Hi

When I came to this forum my knowledge was less than zero about processing I read almost all entire forum and watched all processing videos all over internet now I have little experience to code what I want not that easy for me but I can hardly

Here you can meet very good experience people you can learn from them as I did

The sketch I provided isn’t mine I collected it from multi topics

1 Like

Hi @asymmetric,

if you remove the first push and do the translate before the rotate you are getting this (took my image instead as not having yours)… (see vid below)
You can use the points (for which you’re currently adding spheres) also as gridpoints. If you afterwards connect it to a mesh it would do the same … so my answer would be, yes!

However, you can do whatever you want to do with that height information (There are so many possibilities to play around with it) depending on your imagination. :wink:

Cheers
— mnse

output

1 Like