Wie erzeugt man kachelbares Perlin-Rauschen?


127

Verbunden:

Ich möchte kachelbares Perlin-Rauschen erzeugen. Ich arbeite von Paul Bourkes PerlinNoise*() Funktionen aus, die so aussehen:

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

Verwenden Sie Code wie:

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

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

Gibt Himmel wie

nicht verflüchtigbar

Welches ist nicht kachelbar.

Die Pixelwerte sind 0 -> 256 (Breite und Höhe) und Pixel (0,0) verwendet (x, y) = (0,0) und Pixel (256,256) verwendet (x, y) = (1,1).

Wie kann ich es fliesbar machen?


14
Nur zu Ihrer Information, was Sie dort haben, ist kein Perlin-Rauschen. Es ist fraktales Rauschen. Perlin-Rauschen ist wahrscheinlich die "noise2" -Funktion, die jede Oktave des fraktalen Rauschens erzeugt.
Nathan Reed

Antworten:


80

Es gibt zwei Teile, um fBm-Rauschen wie dieses nahtlos zu kacheln. Zunächst müssen Sie die Perlin-Rauschfunktion selbst kachelbar machen. Hier ist ein Python-Code für eine einfache Perlin-Rauschfunktion, die mit einer beliebigen Periode bis zu 256 arbeitet (Sie können sie beliebig erweitern, indem Sie den ersten Abschnitt ändern):

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

Perlin-Rauschen wird aus einer Summe kleiner "Surflets" erzeugt, die das Produkt eines zufällig orientierten Gradienten und einer trennbaren Polynom-Falloff-Funktion sind. Dies ergibt einen positiven Bereich (gelb) und einen negativen Bereich (blau).

Kernel

Die Surflets haben eine Ausdehnung von 2 × 2 und sind auf die ganzzahligen Gitterpunkte zentriert, sodass der Wert des Perlin-Rauschens an jedem Punkt im Raum durch Summieren der Surflets an den Ecken der Zelle, die sie einnehmen, erzeugt wird.

Summe

Wenn Sie die Verlaufsrichtungen mit einer bestimmten Periode umbrechen lassen, wird das Rauschen selbst nahtlos mit derselben Periode umbrochen. Aus diesem Grund nimmt der obige Code das Gitterkoordinatenmodul für die Periode, bevor es durch die Permutationstabelle gehasht wird.

Der andere Schritt ist, dass Sie beim Summieren der Oktaven die Periode mit der Frequenz der Oktave skalieren möchten. Im Wesentlichen soll jede Oktave das gesamte gerade Bild einmal und nicht mehrmals kacheln:

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

Füge das zusammen und du bekommst so etwas:

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

Tileable FM Noise

Wie Sie sehen können, funktioniert dies in der Tat nahtlos:

fBm Lärm, gefliest

Mit einigen kleinen Optimierungen und Farbzuordnungen ist hier ein Wolkenbild, das 2x2 gekachelt ist:

Wolken!

Hoffe das hilft!


3
Ich bin kein Python-Typ, also frage ich, wie x*2**okonvertiert man nach C? ist es: x*pow(2,o)oder pow(x*2,o)?
idev

7
x*pow(2, o), da die Potenzierung Vorrang vor der Multiplikation hat.
John Calsbeek

1
könnte jemand dies in C konvertieren? Ich habe große Probleme, diesen Code zu verstehen, da ich noch nie etwas mit Python gemacht habe. Was ist zum Beispiel aWert? und ich bin nicht sicher, wie die Funktionen in C konvertieren ... Ich bekomme nur gerade Linien in der Ausgabe.
idev

1
Dies ist definitiv die beste Lösung, solange Sie in Ordnung sind, dass der Bereich Ihres Geräusches an die Form Ihrer Fliese gebunden ist. Dies erlaubt beispielsweise keine willkürlichen Rotationen. Aber wenn Sie so etwas nicht brauchen, ist dies die ideale Antwort.
John Calsbeek

1
Hinweis: Wenn Sie eine andere Größe als 128 generieren möchten, ändern Sie NICHT die numerischen Werte in der Zeile im.putdata(data, 128, 128). (Für diejenigen, die nicht mit Python oder PIL vertraut sind: sie bedeuten Skalierung und Versatz, nicht Bildgröße.)
Antti Kissaniemi

87

Hier ist eine ziemlich clevere Methode, die 4D Perlin-Rauschen verwendet.

Ordnen Sie im Allgemeinen die X-Koordinate Ihres Pixels einem 2D-Kreis und die Y-Koordinate Ihres Pixels einem zweiten 2D-Kreis zu und platzieren Sie diese beiden Kreise orthogonal zueinander im 4D-Raum. Die resultierende Textur ist kachelbar, weist keine offensichtlichen Verzerrungen auf und wiederholt sich nicht wie eine gespiegelte Textur.

Kopieren und Einfügen von Code aus dem Artikel:

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
Das ist definitiv die richtige Antwort. Dimensionen hinzufügen ist ein alter Mathematikertrick. Olinde Rodrigues docet (Sir WR Hamilton docet auch, aber etwas weniger)
FxIII

@FxIII, weißt du, wie dieses Noise4D () implementiert werden soll? Ich möchte dies versuchen, aber ich habe keine Ahnung, wie diese Noise4D () funktionieren sollte.
idev

4
Sie können jede 4D Noise-Funktion verwenden. Simplexrauschen wäre meine Empfehlung. webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
John Calsbeek

2
danke john! Habe es zum Laufen gebracht, Süße! niemand hat es gesagt, aber: das x1, y1, x2, y2 scheint eine Art Skalierung zu sein, je größer der Abstand, desto detaillierter das Rauschen. wenn das jemandem hilft.
idev

5
Beachten Sie, dass dies topologisch der Antwort von bobobobo entspricht: In Ihrem Mapping ist ein 2-Torus in ein ℝ⁴ eingebettet, was ohne die metrischen Verzerrungen möglich ist, die beim Einbetten in ℝ³ unvermeidlich sind.
links um den

22

OK ich habe es. Die Antwort ist, in einem Torus in 3D-Rauschen zu laufen und daraus eine 2D-Textur zu erzeugen.

Torus umhüllt 2 dirs

Code:

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

Ergebnisse:

Einmal:

Fliesenhimmel

Und gekachelt:

zeigt es Fliesen


6
Es funktioniert ein bisschen, aber es sieht so aus, als ob Sie aufgrund der Krümmung des Torus ein paar Verzerrungen bekommen.
Nathan Reed

1
Sie können die Position wirklich nur modulieren, aber ich liebe all die fantastischen / kreativen Antworten auf diese Frage. So viele verschiedene Möglichkeiten, dasselbe zu tun.

Mir ist aufgefallen, dass Sie eigentlich keine 0-1-Werte verwenden möchten, sondern 0-0.9999 ... Werte! Sie würden also Folgendes verwenden: x / width, y / height usw. Andernfalls stimmen die Nähte nicht überein (die gegenüberliegenden Kanten werden exakt auf die gleichen Pixel eingestellt). es sieht auch so aus, als ob die PerlinNoise3D () - Funktion auch eine Begrenzung für den Ergebniswert oder einen Überlauf einiger Pixelwerte benötigt.
idev

@ Nathan, weißt du, wie man die Verzerrung korrigiert?
idev

2
@idev Ich glaube, der Weg, um die Verzerrung zu beheben, besteht darin, die 4D-Methode in der oberen Antwort auf diese Frage zu verwenden. ;)
Nathan Reed

16

Ein einfacher Weg, den ich mir vorstellen kann, wäre, die Ausgabe der Rauschfunktion zu nehmen und sie in ein Bild zu spiegeln / zu spiegeln, das doppelt so groß ist. Es ist schwer zu erklären, daher hier ein Bild: Bildbeschreibung hier eingeben

In diesem Fall ist es ziemlich offensichtlich, was Sie getan haben, als Sie sich das anschauten. Ich kann mir zwei Möglichkeiten vorstellen, dies (möglicherweise :-)) zu lösen:

  1. Sie könnten das größere Bild aufnehmen und dann etwas mehr Rauschen darüber erzeugen, aber (und ich bin mir nicht sicher, ob dies möglich ist) auf die Mitte fokussieren (damit die Ränder gleich bleiben). Es könnte den zusätzlichen Unterschied hinzufügen, der Ihr Gehirn denken lässt, dass es sich nicht nur um Spiegelbilder handelt.

  2. (Ich bin mir auch nicht sicher, ob dies möglich ist.) Sie können versuchen, mit den Eingaben für die Rauschfunktion zu experimentieren, um das ursprüngliche Bild anders zu erzeugen. Sie müssten dies durch Ausprobieren tun, aber suchen Sie nach Funktionen, die Ihre Aufmerksamkeit auf sich ziehen, wenn Sie sie kacheln / spiegeln, und versuchen Sie dann, sie nicht zu generieren.

Hoffe das hilft.


3
Sehr schön aber zu symmetrisch!
Bobobobo

1
@ Bobobobo Das ist, was ich dachte, die anderen Schritte würden lindern. Sie können also mit dieser Methode eine "Basis" generieren und dann weitere Details über das Ganze hinzufügen, damit es so aussieht, als wäre es nicht (so) gespiegelt.
Richard Marskell - Drackir

Wenn Sie so etwas tun, bekommen Sie ein paar seltsame Muster. Insbesondere dieser sieht aus wie ein Schmetterling. Einfache Lösung.
notlesh

Dies war auch mein erster Ansatz, aber es gibt ein Problem, das hier zu sehen ist: dl.dropbox.com/u/6620757/noise_seam.png Wenn Sie eine Flip-Grenze überschreiten, führen Sie zu einer Störung der Rauschfunktion, indem Sie die Steigung der Funktion. Auch wenn Sie oben eine zweite Rauschfunktion anwenden, kann dies in der Ausgabe weiterhin sichtbar sein.
Jherico

Großartige Idee. Dies kann einfach in einem Pixel-Shader mit der Dreieck-Wellen- Funktion durchgeführt werden:tex2d(abs(abs(uv.x)%2.0-1.0), abs(abs(uv.y)%2.0-1.0))
16.

10

Die erste Version dieser Antwort war tatsächlich falsch. Ich habe sie aktualisiert

Ein Verfahren verwenden I ist erfolgreich Lärm Domain gefliest. Mit anderen Worten, machen Sie Ihre noise2()Basisfunktion periodisch. Wenn noise2()periodisch und betaganzzahlig ist, hat das resultierende Rauschen die gleiche Periode wie noise2().

Wie können wir noise2()periodisch machen ? In den meisten Implementierungen verwendet diese Funktion eine Art Gitterrauschen. Das heißt, es werden Zufallszahlen an ganzzahligen Koordinaten abgerufen und interpoliert. Zum Beispiel:

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

Diese Funktion kann trivial modifiziert werden, um mit einer ganzzahligen Periode periodisch zu werden. Fügen Sie einfach eine Zeile hinzu:

integer_X = integer_X % Period

vor der Berechnung v1und v2. Auf diese Weise werden Werte an ganzzahligen Koordinaten alle Periodeneinheiten wiederholt, und durch Interpolation wird sichergestellt, dass die resultierende Funktion reibungslos ist.

Beachten Sie jedoch, dass dies nur funktioniert, wenn die Periode größer als 1 ist. Wenn Sie dies also für die Erstellung nahtloser Texturen verwenden möchten, müssen Sie ein Quadrat aus Periode x Periode und nicht 1x1 abtasten.


Aber wie macht man das noise2periodisch (mit einer kurzen Periode wie 1 Einheit)? Ich denke, das ist die Frage, die sich letztendlich stellt. Standard-Perlin-Rauschen ist periodisch mit einer Periode von 256 auf jeder Achse, Sie möchten jedoch ein modifiziertes Rauschen mit einer kleineren Periode.
Nathan Reed

@Nathan Reed Wenn Sie anrufen , noise2wie vorgeschlagen, Sie werden regelmäßig Ergebnisse erhalten, ob die Funktion selbst periodisch ist oder nicht. Weil sich die Argumente um jede Einheit drehen.
Nevermind

1
Aber dann bekommt man Nähte an den Gitterlinien, nicht wahr? Da gibt es keine Garantie, dass noise2 (0, 0.999) irgendetwas in der Nähe von noise2 (0, 0) ist, es sei denn, ich habe etwas übersehen.
Nathan Reed

1
@ Nathan Reed Das ist ein guter Punkt. Tatsächlich habe ich meinen alten Code noch einmal überprüft und es stellte sich heraus, dass ich falsch lag. Ich werde die Antwort jetzt bearbeiten.
Nevermind

Toll! Das ist jetzt tatsächlich eine gute Antwort. +1 :)
Nathan Reed

6

Eine andere Alternative besteht darin, mit libnoise-Bibliotheken Rauschen zu erzeugen. Sie können nahtlos über eine theoretisch unendliche Menge an Raum Rauschen erzeugen.

Schauen Sie sich Folgendes an: http://libnoise.sourceforge.net/tutorials/tutorial3.html#tile

Es gibt auch einen XNA-Port für das oben Genannte unter : http://bigblackblock.com/tools/libnoisexna

Wenn Sie am Ende den XNA-Port verwenden, können Sie Folgendes tun:

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 ist die Funktion, die aufgerufen wird, um die Abschnitte in jeder Richtung abzurufen, die sich nahtlos mit den übrigen Texturen verbinden.

Natürlich ist diese Methode teurer als nur eine einzige Textur, die über mehrere Oberflächen hinweg verwendet werden kann. Wenn Sie nach zufälligen kachelbaren Texturen suchen, könnte dies für Sie von Interesse sein.


6

Obwohl es hier einige Antworten gibt, die funktionieren würden, sind die meisten kompliziert, langsam und problematisch.

Alles, was Sie wirklich tun müssen, ist die Verwendung einer Funktion zur periodischen Geräuscherzeugung. Das ist es!

Eine exzellente Public Domain-Implementierung basierend auf Perlins "fortgeschrittenem" Rauschalgorithmus finden Sie hier . Die Funktion, die Sie benötigen, ist pnoise2. Der Code wurde von Stefan Gustavson geschrieben, der genau zu diesem Thema hier einen spitzen Kommentar abgegeben hat und wie andere den falschen Ansatz gewählt haben. Hör Gustavson zu, er weiß wovon er spricht.

In Bezug auf die verschiedenen sphärischen Projektionen haben einige angedeutet: Nun, sie funktionieren im Wesentlichen (langsam), aber sie erzeugen auch eine 2D-Textur, die eine abgeflachte Kugel ist, so dass die Kanten stärker verdichtet würden, was wahrscheinlich einen unerwünschten Effekt hervorruft. Wenn Sie beabsichtigen, Ihre 2D-Textur auf eine Kugel zu projizieren, ist dies natürlich der richtige Weg, aber danach wurde nicht gefragt.


4

Hier ist eine viel einfachere Möglichkeit, gekachelte Geräusche zu erzeugen:

Kacheln von Perlin-Rauschen aus Shadertoy-Code

Sie verwenden einen modularen Umgriff für jede Skala des Geräusches. Diese passen sich den Rändern des Bereichs an, unabhängig davon, welche Frequenzskala Sie verwenden. Sie müssen also nur normales 2D-Rauschen verwenden, das viel schneller ist. Hier ist der Live-WebGL-Code, der unter ShaderToy zu finden ist: https://www.shadertoy.com/view/4dlGW2

Die drei obersten Funktionen erledigen die gesamte Arbeit, und fBM wird ein Vektor x / y in einem Bereich von 0,0 bis 1,0 übergeben.

// 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
Ihr Bildlink ist nicht mehr verfügbar. Ich habe es nach bestem Wissen erraten und durch einen Screenshot der Ausgabe des von Ihnen geposteten Shadertoy-Codes ersetzt. Wenn dies nicht der Fall ist, laden Sie das gewünschte Image direkt auf den Stack Exchange-Server hoch.
Pikalek

3

Ich hatte einige nicht schlechte Ergebnisse bei der Interpolation in der Nähe der Kanten der Kachel (kantenumwickelt), aber es hängt davon ab, welchen Effekt Sie erzielen möchten und welche genauen Rauschparameter vorliegen. Funktioniert hervorragend bei etwas verschwommenen Geräuschen, nicht so gut bei spitzen / feinkörnigen.


0

Ich habe diesen Thread auf der Suche nach einer Antwort auf ein ähnliches Problem überprüft und dann vom Entwickler dieses Python-Codes eine saubere und kompakte Lösung zur Erzeugung von fraktalem Rauschen aus Perlin / Simplex-Rauschen erhalten. Der aktualisierte Code ist in dieser (abgeschlossenen) Ausgabe enthalten und kann fortgesetzt werden, indem die Farbverläufe für die rechte Seite des "Generators" mit denen auf der linken Seite (und für oben und unten) gleichgesetzt werden, z. B. 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]

Scheint eine elegante und saubere Lösung zu sein, ich vermeide es, den gesamten Code hier zu kopieren (da es nicht meine eigene Lösung ist), aber sie ist unter dem oben angegebenen Link verfügbar. Hoffe, dass dies für jemanden nützlich sein kann, der ein kachelbares fraktales 2D-Bild wie das von mir benötigte ohne Artefakte oder Verzerrungen erstellen möchte.

fliesbares fraktales gelände

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.