Archivi tag: perlin noise

2D Perlin Noise

Nei due post precedenti abbiamo sempre usato la funzione noise() in modo mono dimensionale. È arrivato il momento di fare un passo in avanti e aggiungere la seconda dimensione.

Ripasso: 1D Perlin Noise

Quando parliamo di Perlin Noise mono dimensionale dobbiamo immaginare i valori su una linea temporale orizzontale; quando noi chiediamo alla funzione di restituirci un determinato valore di noise, tale valore sarà correlato a quello precedente e a quello successivo. Nell’immagine viene rappresentato il valore di noise con parametro xOff pari a 3.32. Come nel post precedente, l’incremento che ho usato per generare l’immagine è 0.02.

Perlin Noise Monodimensionale

2D Perlin Noise

Quando aggiungiamo una dimensione, invece, dobbiamo pensare a una griglia i cui valori sono correlati tra loro sia sull’asse delle x che su quello delle y.

2D Perlin Noise

Come si evince dall’immagine, la situazione diventa più complicata perché se analizziamo singolarmente i punti centrali rosso o blu intuiamo facilmente la correlazione con i punti che li circondano. Se li consideriamo entrambi contemporaneamente, invece, notiamo subito come ci siano dei punti in comune che dovranno essere a loro volta correlati tra loro.

Questo è un esempio molto semplificato: provate a immaginare se ciascuno dei punti disegnati fosse un pixel!

Generiamo una texture

È arrivato il momento di sporcarsi le mani e scrivere un po’ di codice: lo scopo del programma di oggi è generare una texture con Processing utilizzando Perlin Noise bidimensionale.

Partiamo da un’analisi del risultato finale per capire tutti i passaggi e il codice che ci serve:

Texture generata con Perlin Noise bidimensionale

Per generare la texture dobbiamo prendere ciascun pixel della nostra finestra facendo in modo che il suo colore sia correlato ai pixel vicini: è facile notare come ci siano delle aree più scure e altre più chiare e che, in generale, la texture generata sia uniforme.

Per farvi capire la differenza, nell’immagine qui sotto ho sostituito nel programma la funzione noise() con random():

Random Texture Processing
Sostituendo la funzione noise() con random() il risultato è molto differente.

Per lavorare con i pixel utilizzeremo le funzioni loadPixels() updatePixels() che avremo modo di analizzare in modo approfondito in futuro. Per il momento vi basti sapere che la prima funzione carica tutti i pixel della finestra in un array chiamato pixels[] e che la seconda ricarica i pixel nella finestra dopo che sono stati modificati.

Un altro punto molto importante è sapere che, benché i pixel presenti in una finestra siano un insieme di righe e colonne, nell’array pixels[] vengono salvati con numero progressivo.

Per modificare il colore di ciascun pixel della finestra abbiamo bisogno di due cicli for annidati: uno per i valori di x, l’altro per quelli di y. La funzione noise() entra in gioco proprio in questa fase: attraverso due variabili – xOff e yOff – imposteremo il colore di ciascun pixel.

Ecco il codice finale:

/*
 * Texture con Perlin Noise 2D
 * by Federico Pepe
 *
 */

float increment = 0.015;

void setup() {
  size(500, 500);
}

void draw() {
  // Carico i pixel della finestra
  loadPixels();
  float xOff = 0;
  for(int x = 0; x < width; x++) {
    xOff += increment;
    float yOff = 0;
    for (int y = 0; y < height; y++) {
      yOff += increment;
      // Ottengo il valore noise passando le variabili xOff e yOff.
      float bright = noise(xOff, yOff) * 255;
      // Riassegno a ogni pixel il nuovo colore
      pixels[x+y*width] = color(bright);
    }
  }
  // Aggiorno tutti i pixel della finestra
  updatePixels();
}

Domanda: perché non ho inizializzato le due variabili xOff e yOff all'inizio del programma? Cosa succede se non reimposto a 0 la variabile yOff a ogni ciclo?

Per aggiungere l'interazione con l'utente, aggiungiamo subito dopo updatePixels() la seguente riga: increment = map(mouseX, 0, width, 0.1, 0.01);

In questo modo rimapperemo la posizione X del mouse che può andare da 0 alla larghezza della finestra in un nuovo range compreso tra 0.1 e 0.01.

Random e Perlin Noise

In passato abbiamo utilizzato la funzione random() per rendere più interessanti i nostri sketch. Per fare un breve ripasso: vi ricordo che tale funzione accetta uno o due argomenti e restituisce sempre un numero casuale di tipo float. random(10); darà come risultato un numero compreso tra 0 e 10 mentre random(5, 10); restituirà un valore casuale compreso tra un minimo (5) e un massimo (10).

Random è una funzione bellissima perché, se usata nel modo giusto, può generare risultati davvero particolari senza complicare troppo il codice. Il problema a cui andiamo incontro, però, è che utilizzando questa funzione perdiamo il controllo del nostro sketch perché ci affidiamo completamente alla casualità: spesso i risultati possono sembrare molto caotici e privi di senso.

Ecco che in alcuni casi la funzione noise() ci può aiutare a creare una casualità “controllata”.

Cos’è il Perlin Noise?

Senza andare troppo nel dettaglio, il Perlin Noise prende il nome da Ken Perlin, un professore di informatica della New York University che negli anni ’80 ha sviluppato questa tipo di rumore per generare texture in modo procedurale. I risultati della sua ricerca hanno reso possibile, nel campo della computer graphics, la rappresentazione della complessità della natura attraverso risultati organici generati da un algoritmo.

Aggiornamento 2.06.2016: se siete interessati a capire meglio e in modo davvero approfondito come funziona il Perlin Noise, consiglio la lettura di questo articolo (in inglese).

Nel manuale di Processing si trova un’informazione molto importante in merito a questa funzione:

In contrast to the random() function, Perlin noise is defined in an infinite n-dimensional space, in which each pair of coordinates corresponds to a fixed semi-random value (fixed only for the lifespan of the program). The resulting value will always be between 0.0 and 1.0.

A differenza della funzione random(), noise() restituirà un risultato sempre compreso tra 0 e 1. Teniamo a mente questa informazione e facciamo un esempio pratico.

Random

// Random e Perlin Noise

void setup() {
  size(500, 500);
}

void draw() {
  background(255);
  float x = random(width);
  ellipse(x, height/2, 50, 50);
}

Questo programma mostrerà un cerchio con posizione x totalmente casuale e y pari alla metà dell’altezza della finestra. Nota: Possiamo ridurre la velocità di esecuzione con la funzione frameRate().

Ecco l’output grafico in formato GIF:

Utilizzo della funzione random in Processing

Prima di passare all’esempio con la funzione noise() è necessario fare un’altra precisazione. Entrambe le funzioni che stiamo analizzando sono strettamente legate al concetto di tempo: random() genera un nuovo valore casuale ogni volta che viene chiamata la funzione quindi, nell’esempio precedente, 60 volte al secondo; noise(), invece, riesce a generare risultati organici perché ciascun valore è legato temporalmente a quello precedente e al successivo.

Possiamo simulare lo scorrimento del tempo utilizzando una variabile che incrementiamo a ogni ciclo di draw; più piccolo sarà l’incremento, maggiore sarà la vicinanza dei valori restituiti dalla funzione.

Noise

// Random e Perlin Noise

// Creiamo una variabile per simulare l'avanzamento del tempo
float time; 

void setup() {
  size(500, 500);
}

void draw() {
  background(255);
  /*
   * Dal momento che noise restituisce un valore compreso tra 0 e 1, moltiplico
   * il numero per la larghezza della finestra per ottenere un valore compreso tra
   * 0 e 500
  */
  float x = noise(time) * width;
  ellipse(x, height/2, 50, 50);
  // A ogni ciclo draw, aumentiamo la variabile di tempo
  time += 0.05;
}

Per rendere l’esempio il più chiaro possibile ed evitare di rendere questo post troppo verboso, ho inserito diversi commenti nel codice. Come prima, vi mostro l’output grafico in formato GIF:

Utilizzo del Perlin Noise in Processing

Grazie alle due immagini animato, è semplice notare quanto i movimenti del secondo cerchio siano più organici rispetto al precedente. Se volete fare degli esperimenti partendo dal mio esempio, vi consiglio di modificare l’incremento della variabile time.

Cosa succede se scriviamo time += 1; oppure time += 0.01;?