Come si genera il rumore Perlin piastrellabile?


127

Relazionato:

Vorrei generare un rumore Perlin piastrellabile. Sto lavorando dalle funzioni di Paul Bourke PerlinNoise*() , che sono così:

// alpha is the "division factor" (how much to damp subsequent octaves with (usually 2))
// beta is the factor that multiplies your "jump" into the noise (usually 2)
// n is the number of "octaves" to add in
double PerlinNoise2D(double x,double y,double alpha,double beta,int n)
{
   int i;
   double val,sum = 0;
   double p[2],scale = 1;

   p[0] = x;
   p[1] = y;
   for (i=0;i<n;i++) {
      val = noise2(p);
      sum += val / scale;
      scale *= alpha;
      p[0] *= beta;
      p[1] *= beta;
   }
   return(sum);
}

Usando codice come:

real val = PerlinNoise2D( x,y, 2, 2, 12 ) ; // test

return val*val*skyColor + 2*val*(1-val)*gray + (1-val)*(1-val)*cloudColor ;

Dà il cielo come

nontileable

Che non è piastrellabile.

I valori dei pixel sono 0-> 256 (larghezza e altezza) e pixel (0,0) usa (x, y) = (0,0) e pixel (256.256) usa (x, y) = (1,1)

Come posso renderlo piastrellabile?


14
Cordiali saluti, quello che hai lì non è il rumore di Perlin; è rumore frattale. Il rumore di Perlin è probabilmente la funzione "noise2" che genera ogni ottava del rumore frattale.
Nathan Reed,

Risposte:


80

Ci sono due parti per creare un rumore fBm perfettamente piastrellabile come questo. Innanzitutto, è necessario rendere piastrellabile la funzione di rumore Perlin stessa. Ecco un po 'di codice Python per una semplice funzione di rumore Perlin che funziona con qualsiasi periodo fino a 256 (puoi estenderlo banalmente quanto vuoi modificando la prima sezione):

import random
import math
from PIL import Image

perm = range(256)
random.shuffle(perm)
perm += perm
dirs = [(math.cos(a * 2.0 * math.pi / 256),
         math.sin(a * 2.0 * math.pi / 256))
         for a in range(256)]

def noise(x, y, per):
    def surflet(gridX, gridY):
        distX, distY = abs(x-gridX), abs(y-gridY)
        polyX = 1 - 6*distX**5 + 15*distX**4 - 10*distX**3
        polyY = 1 - 6*distY**5 + 15*distY**4 - 10*distY**3
        hashed = perm[perm[int(gridX)%per] + int(gridY)%per]
        grad = (x-gridX)*dirs[hashed][0] + (y-gridY)*dirs[hashed][1]
        return polyX * polyY * grad
    intX, intY = int(x), int(y)
    return (surflet(intX+0, intY+0) + surflet(intX+1, intY+0) +
            surflet(intX+0, intY+1) + surflet(intX+1, intY+1))

Il rumore di Perlin è generato da una somma di piccole "superfici" che sono il prodotto di un gradiente orientato in modo casuale e di una funzione di decadimento polinomiale separabile. Questo dà una regione positiva (gialla) e una regione negativa (blu)

nocciolo

Le surflet hanno un'estensione di 2x2 e sono centrate sui punti reticolari interi, quindi il valore del rumore di Perlin in ciascun punto dello spazio viene prodotto sommando le surflet agli angoli della cella che occupa.

Somma

Se si fanno avvolgere le direzioni del gradiente con un certo periodo, il rumore stesso si avvolgerà perfettamente con lo stesso periodo. Questo è il motivo per cui il codice sopra prende la coordinata reticolare modulo il periodo prima di eseguire l'hashing attraverso la tabella di permutazione.

L'altro passo è che quando si sommano le ottave si desidera ridimensionare il periodo con la frequenza dell'ottava. In sostanza, ogni ottava dovrà affiancare l'intera immagine giusta una volta, anziché più volte:

def fBm(x, y, per, octs):
    val = 0
    for o in range(octs):
        val += 0.5**o * noise(x*2**o, y*2**o, per*2**o)
    return val

Mettilo insieme e otterrai qualcosa del genere:

size, freq, octs, data = 128, 1/32.0, 5, []
for y in range(size):
    for x in range(size):
        data.append(fBm(x*freq, y*freq, int(size*freq), octs))
im = Image.new("L", (size, size))
im.putdata(data, 128, 128)
im.save("noise.png")

Disturbo fBm piastrellabile

Come puoi vedere, questo effettivamente affianca perfettamente:

fBm Rumore, piastrellato

Con alcune piccole modifiche e mappatura dei colori, ecco un'immagine nuvola piastrellata 2x2:

Nuvole!

Spero che sia di aiuto!


3
non sono un pitone, quindi chiedo, come si x*2**oconverte in C? è: x*pow(2,o)o pow(x*2,o)?
idev,

7
x*pow(2, o), poiché l'espiazione ha una precedenza maggiore rispetto alla moltiplicazione.
John Calsbeek,

1
qualcuno potrebbe convertirlo in C? ho enormi problemi nel comprendere questo codice, dato che non ho mai fatto nulla con Python. per esempio qual è il avalore? e non sono sicuro di come le funzioni si convertano in C ... ottengo solo linee rette nell'output.
idev,

1
Questa è sicuramente la soluzione migliore, purché tu stia bene con il dominio del tuo rumore legato alla forma della tua piastrella. Ad esempio, ciò non consente rotazioni arbitrarie. Ma se non hai bisogno di nulla del genere, questa è la risposta ideale.
John Calsbeek,

1
Nota: se si desidera generare dimensioni diverse da 128, NON modificare i valori numerici sulla riga im.putdata(data, 128, 128). (Per chi non ha familiarità con Python o PIL: significano scala e offset, non dimensione dell'immagine.)
Antti Kissaniemi,

87

Ecco un modo piuttosto intelligente che utilizza il rumore 4D Perlin.

Fondamentalmente, mappa la coordinata X del tuo pixel su un cerchio 2D e la coordinata Y del tuo pixel su un secondo cerchio 2D e posiziona quei due cerchi ortogonali tra loro nello spazio 4D. La trama risultante è piastrellabile, non presenta distorsioni evidenti e non si ripete come una trama speculare.

Copia-incolla codice dall'articolo:

for x=0,bufferwidth-1,1 do
    for y=0,bufferheight-1,1 do
        local s=x/bufferwidth
        local t=y/bufferheight
        local dx=x2-x1
        local dy=y2-y1

        local nx=x1+cos(s*2*pi)*dx/(2*pi)
        local ny=y1+cos(t*2*pi)*dy/(2*pi)
        local nz=x1+sin(s*2*pi)*dx/(2*pi)
        local nw=y1+sin(t*2*pi)*dy/(2*pi)

        buffer:set(x,y,Noise4D(nx,ny,nz,nw))
    end
end

3
Questa è sicuramente la risposta giusta. Aggiungere dimensioni è un vecchio trucco matematico. Docet di Olinde Rodrigues (anche il docet di Sir WR Hamilton, ma leggermente inferiore)
FxIII,

@FxIII, sai come dovrebbe essere implementato questo Noise4D ()? vorrei provare questo ma non ho idea di come dovrebbe funzionare Noise4D ().
idev,

4
È possibile utilizzare qualsiasi funzione di rumore 4D. Il rumore simplex sarebbe la mia raccomandazione. webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
John Calsbeek,

2
grazie giovanni! ha funzionato, dolcezza! nessuno l'ha detto, ma: x1, y1, x2, y2 sembra essere una sorta di ridimensionamento, la distanza maggiore, il rumore dettagliato. se questo aiuta qualcuno.
idev,

5
Nota che questo è topologicamente equivalente alla risposta di bobobobo: la tua mappatura incorpora un 2-toro in un ℝ⁴, che è possibile senza le distorsioni metriche che inevitabilmente ottieni quando lo incorpori in ℝ³.
sinistra circa l'

22

Ok ho capito. La risposta è camminare in un toro con rumore 3D, generando una trama 2D da esso.

il toro avvolge 2 dir

Codice:

Color Sky( double x, double y, double z )
{
  // Calling PerlinNoise3( x,y,z ),
  // x, y, z _Must be_ between 0 and 1
  // for this to tile correctly
  double c=4, a=1; // torus parameters (controlling size)
  double xt = (c+a*cos(2*PI*y))*cos(2*PI*x);
  double yt = (c+a*cos(2*PI*y))*sin(2*PI*x);
  double zt = a*sin(2*PI*y);
  double val = PerlinNoise3D( xt,yt,zt, 1.5, 2, 12 ) ; // torus

  return val*val*cloudWhite + 2*val*(1-val)*gray + (1-val)*(1-val)*skyBlue ;
}

risultati:

Una volta:

cielo piastrellabile

E piastrellato:

mostrandole tessere


6
Funziona un po ', ma sembra che tu stia ottenendo un sacco di distorsione a causa della curvatura del toro.
Nathan Reed,

1
puoi davvero solo modulare la posizione, ma adoro tutte le risposte fantastiche / creative a questa domanda. Tanti modi diversi di fare la stessa cosa.

ho notato che in realtà non vuoi usare i valori 0-1, ma 0-0.9999 ... valori! quindi dovresti usare: x / larghezza, y / altezza ecc. altrimenti le cuciture non corrispondono (rende i bordi opposti esattamente gli stessi pixel). inoltre, sembra che la funzione PerlinNoise3D () debba essere bloccata anche per il valore del risultato o che alcuni valori di pixel traboccino.
idev,

@Nathan, sai come correggere la distorsione?
idev,

2
@idev Credo che il modo per correggere la distorsione sia usare il metodo 4D nella risposta migliore di questa domanda. ;)
Nathan Reed l'

16

Un modo semplice a cui riesco a pensare sarebbe quello di prendere l'output della funzione noise e rispecchiarlo / capovolgerlo in un'immagine doppia del doppio. È difficile da spiegare, quindi ecco un'immagine: inserisci qui la descrizione dell'immagine

Ora, in questo caso, è abbastanza ovvio quello che hai fatto quando lo guardavi. Posso pensare a due modi per (possibilmente :-)) risolvere questo:

  1. Potresti prendere quell'immagine più grande e quindi generare più rumore sopra di essa ma (e non sono sicuro che sia possibile) focalizzata verso il centro (quindi i bordi rimangono gli stessi). Potrebbe aggiungere un po 'di differenza in più che farebbe pensare al tuo cervello non solo immagini speculari.

  2. (Non sono nemmeno sicuro che sia possibile) Potresti provare a armeggiare con gli ingressi della funzione noise per generare l'immagine iniziale in modo diverso. Dovresti farlo per tentativi ed errori, ma cerca le funzionalità che attirano la tua attenzione quando lo piastrelli / specchi e quindi cerca di farlo non generare quelli.

Spero che sia di aiuto.


3
Molto bello ma troppo simmetrico!
Bobobobo,

1
@bobobobo Ecco cosa pensavo che gli altri passaggi avrebbero alleviato. Quindi, potresti generare una "base" usando questo metodo, e quindi aggiungere qualche dettaglio in più per far sembrare che non sia (così) rispecchiato.
Richard Marskell - Drackir,

Inizi a ottenere strani schemi quando fai questo tipo di cose. Questo in particolare sembra un po 'come una farfalla. Soluzione facile, comunque.
notlesh,

Anche questo è stato il mio primo approccio, ma ha un problema, visibile qui: dl.dropbox.com/u/6620757/noise_seam.png Mentre attraversi un confine di vibrazione, causi un disgiunzione nella funzione del rumore invertendo istantaneamente la pendenza del funzione. Anche se si applica una seconda funzione di rumore in alto, questa potrebbe essere ancora visibile nell'output.
Jherico,

Grande idea. Questo può essere fatto facilmente in un pixel shader usando la funzione wave triangolare :tex2d(abs(abs(uv.x)%2.0-1.0), abs(abs(uv.y)%2.0-1.0))
tigrou,

10

La prima versione di questa risposta era in realtà sbagliata, l'ho aggiornata

Un metodo che ho usato con successo è rendere il dominio del rumore piastrellato. In altre parole, rendi noise2()periodica la tua funzione di base . Se noise2()è periodico ed betaè intero, il rumore risultante avrà lo stesso periodo di noise2().

Come possiamo fare noise2()periodici? Nella maggior parte delle implementazioni, questa funzione utilizza un qualche tipo di rumore reticolare. Cioè, ottiene numeri casuali su coordinate intere e li interpola. Per esempio:

function InterpolatedNoise_1D(float x)

  integer_X    = int(x)
  fractional_X = x - integer_X

  v1 = SmoothedNoise1(integer_X)
  v2 = SmoothedNoise1(integer_X + 1)

  return Interpolate(v1 , v2 , fractional_X)

end function

Questa funzione può essere banalmente modificata per diventare periodica con periodo intero. Aggiungi semplicemente una riga:

integer_X = integer_X % Period

prima di calcolare v1e v2. In questo modo, i valori alle coordinate intere ripeteranno ogni unità del Periodo e l'interpolazione garantirà che la funzione risultante sia regolare.

Si noti, tuttavia, che questo funziona solo quando Periodo è maggiore di 1. Quindi, per usarlo effettivamente nel creare trame senza soluzione di continuità, è necessario campionare un quadrato Periodo x Periodo, non 1x1.


Ma come si fa a fare noise2periodici (con un breve periodo come 1 unità)? Penso che sia quello che la domanda sta ponendo alla fine. Il rumore standard Perlin è periodico con un periodo di 256 su ciascun asse ma si desidera un rumore modificato con un periodo più piccolo.
Nathan Reed,

@ Nathan Reed Se si chiama noise2come suggerito, si sarà ottenere risultati periodici, se la funzione stessa è periodica o meno. Perché gli argomenti si avvolgono ogni 1 unità.
Nevermind,

1
Ma poi ottieni cuciture sulle linee della griglia, vero? Dal momento che non esiste alcuna garanzia che noise2 (0, 0,999) sia vicino a noise2 (0, 0), a meno che non mi sia perso qualcosa.
Nathan Reed,

1
@Nathan Reed Questo è un buon punto. In effetti, ho appena ricontrollato il mio vecchio codice e risulta che mi sbagliavo. Modificherò la risposta ora.
Nevermind,

Grande! Questa è in realtà una buona risposta ora. +1 :)
Nathan Reed,

6

Un'altra alternativa è generare rumore usando le librerie libnoise. È possibile generare rumore su una quantità infinita teorica di spazio, senza soluzione di continuità.

Dai un'occhiata a quanto segue: http://libnoise.sourceforge.net/tutorials/tutorial3.html#tile

C'è anche una porta XNA di quanto sopra su: http://bigblackblock.com/tools/libnoisexna

Se finisci per usare la porta XNA, puoi fare qualcosa del genere:

Perlin perlin = new Perlin();
perlin.Frequency = 0.5f;                //height
perlin.Lacunarity = 2f;                 //frequency increase between octaves
perlin.OctaveCount = 5;                 //Number of passes
perlin.Persistence = 0.45f;             //
perlin.Quality = QualityMode.High;
perlin.Seed = 8;

//Create our 2d map
Noise2D _map = new Noise2D(CHUNKSIZE_WIDTH, CHUNKSIZE_HEIGHT, perlin);

//Get a section
_map.GeneratePlanar(left, right, top, down);

GeneratePlanar è la funzione da chiamare per ottenere le sezioni in ogni direzione che si collegheranno perfettamente con il resto delle trame.

Naturalmente, questo metodo è più costoso della semplice trama singola che può essere utilizzata su più superfici. Se stai cercando di creare trame piastrellabili casuali, questo potrebbe essere qualcosa che ti interessa.


6

Anche se ci sono alcune risposte che potrebbero funzionare, la maggior parte di esse sono complicate, lente e problematiche.

Tutto quello che devi fare è utilizzare una funzione di generazione periodica del rumore. Questo è tutto!

Qui puoi trovare un'eccellente implementazione di dominio pubblico basata sull'algoritmo di rumore "avanzato" di Perlin . La funzione che ti serve è pnoise2. Il codice è stato scritto da Stefan Gustavson, che qui ha fatto un commento puntuale esattamente su questo problema e su come gli altri hanno adottato un approccio sbagliato. Ascolta Gustavson, sa di cosa sta parlando.

Per quanto riguarda le varie proiezioni sferiche che alcuni hanno suggerito qui: beh, in sostanza funzionano (lentamente), ma producono anche una trama 2D che è una sfera appiattita, in modo che i bordi si condensino più, producendo probabilmente un effetto indesiderato. Naturalmente, se intendi proiettare la tua trama 2D su una sfera, questa è la strada da percorrere, ma non è quello che ti è stato chiesto.


4

Ecco un modo molto più semplice per fare il rumore piastrellato:

rumore perlin di piastrellatura dal codice shadertoy

Si utilizza un avvolgimento modulare per ciascuna scala del rumore. Si adattano ai bordi dell'area, indipendentemente dalla scala di frequenza utilizzata. Quindi devi solo usare il normale rumore 2D che è molto più veloce. Ecco il codice WebGL live che puoi trovare su ShaderToy: https://www.shadertoy.com/view/4dlGW2

Le tre funzioni principali svolgono tutto il lavoro e fBM viene passato un vettore x / y in un intervallo da 0,0 a 1,0.

// Tileable noise, for creating useful textures. By David Hoskins, Sept. 2013.
// It can be extrapolated to other types of randomised texture.

#define SHOW_TILING
#define TILES 2.0

//----------------------------------------------------------------------------------------
float Hash(in vec2 p, in float scale)
{
    // This is tiling part, adjusts with the scale...
    p = mod(p, scale);
    return fract(sin(dot(p, vec2(35.6898, 24.3563))) * 353753.373453);
}

//----------------------------------------------------------------------------------------
float Noise(in vec2 x, in float scale )
{
    x *= scale;

    vec2 p = floor(x);
    vec2 f = fract(x);
    f = f*f*(3.0-2.0*f);
    //f = (1.0-cos(f*3.1415927)) * .5;
    float res = mix(mix(Hash(p,                  scale),
        Hash(p + vec2(1.0, 0.0), scale), f.x),
        mix(Hash(p + vec2(0.0, 1.0), scale),
        Hash(p + vec2(1.0, 1.0), scale), f.x), f.y);
    return res;
}

//----------------------------------------------------------------------------------------
float fBm(in vec2 p)
{
    float f = 0.4;
    // Change starting scale to any integer value...
    float scale = 14.0;
    float amp = 0.55;
    for (int i = 0; i < 8; i++)
    {
        f += Noise(p, scale) * amp;
        amp *= -.65;
        // Scale must be multiplied by an integer value...
        scale *= 2.0;
    }
    return f;
}

//----------------------------------------------------------------------------------------
void main(void)
{
    vec2 uv = gl_FragCoord.xy / iResolution.xy;

#ifdef SHOW_TILING
    uv *= TILES;
#endif

    // Do the noise cloud (fractal Brownian motion)
    float bri = fBm(uv);

    bri = min(bri * bri, 1.0); // ...cranked up the contrast for no reason.
    vec3 col = vec3(bri);

#ifdef SHOW_TILING
    vec2 pixel = (TILES / iResolution.xy);
    // Flash borders...
    if (uv.x > pixel.x && uv.y > pixel.y                                        // Not first pixel
    && (fract(uv.x) < pixel.x || fract(uv.y) < pixel.y) // Is it on a border?
    && mod(iGlobalTime-2.0, 4.0) < 2.0)                 // Flash every 2 seconds
    {
        col = vec3(1.0, 1.0, 0.0);
    }
#endif
    gl_FragColor = vec4(col,1.0);
}

1
Il tuo link immagine è morto. Ho fatto un'ipotesi migliore e l'ho sostituito con una schermata dell'output del codice shadertoy che hai pubblicato. Se ciò non è corretto, ricaricare l'immagine desiderata direttamente sul server Stack Exchange.
Pikalek,

3

Ho avuto alcuni risultati non negativi interpolati vicino ai bordi della piastrella (fasciata), ma dipende dall'effetto che stai cercando di ottenere e dai parametri di rumore esatti. Funziona alla grande per il rumore un po 'sfocato, non così buono con quelli appuntiti / a grana fine.


0

Stavo controllando questo thread alla ricerca di una risposta a un problema simile, quindi ho ottenuto una soluzione pulita e compatta dallo sviluppatore di questo codice Python per generare rumore frattale da rumore perlin / simplex. Il codice aggiornato viene fornito in questo numero (chiuso) e può essere ripreso impostando i gradienti per il lato destro del "generatore" pari a quelli sul lato sinistro (e lo stesso per l'alto e il basso), come in

# Gradients
angles = 2*np.pi*np.random.rand(res[0]+1, res[1]+1)
gradients = np.dstack((np.cos(angles), np.sin(angles)))
# Make the noise tileable
gradients[-1,:] = gradients[0,:]
gradients[:,-1] = gradients[:,0]

Sembra una soluzione elegante e pulita, evito di copiare l'intero codice qui (poiché non è la mia soluzione), ma è disponibile al link indicato sopra. Spero che questo possa essere utile a qualcuno che cerca di produrre un'immagine 2d frattale piastrellabile come questa di cui avevo bisogno, priva di artefatti o distorsioni.

terreno frattale piastrellabile

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.