Jak generujesz hałas Perlina, który można kafelkować?

127

Związane z:

Chciałbym generować hałas Perlina. Pracuję z funkcji Paula Bourke PerlinNoise*() , które są takie:

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

Używanie kodu takiego jak:

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

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

Daje niebo jak

nieściągalne

Których nie można kafelkować.

Wartości pikseli wynoszą 0–> 256 (szerokość i wysokość), a piksel (0,0) używa (x, y) = (0,0), a piksel (256,256) używa (x, y) = (1,1)

Jak mogę ustawić kafelki?

Bobobobo
źródło
14
Tylko do twojej wiadomości, to, co tam masz, to nie hałas Perlina; to fraktalny hałas. Szum Perlina jest prawdopodobnie funkcją „szumu2” generującą każdą oktawę hałasu fraktalnego.
Nathan Reed

Odpowiedzi:

80

Bezproblemowo kafelkowy hałas fBm składa się z dwóch części. Najpierw musisz ustawić funkcję szumu Perlina jako kafelkową. Oto kod Pythona dla prostej funkcji szumu Perlina, która działa z dowolnym okresem do 256 (możesz w prosty sposób rozszerzyć go tak, jak chcesz, modyfikując pierwszą sekcję):

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

Hałas Perlina powstaje ze zsumowania małych „surfletów”, które są wynikiem losowo zorientowanego gradientu i oddzielnej funkcji wielomianu opadania. Daje to region dodatni (żółty) i region ujemny (niebieski)

Jądro

Surflety mają zasięg 2x2 i są wyśrodkowane na całkowitych punktach siatki, więc wartość hałasu Perlina w każdym punkcie przestrzeni jest wytwarzana przez zsumowanie surfletów w rogach zajmowanej przez nie komórki.

Podsumowanie

Jeśli kierunki gradientu zostaną zawinięte z pewnym okresem, sam szum zostanie następnie płynnie zawinięty z tym samym okresem. Dlatego powyższy kod pobiera modulo współrzędnej sieci przed okresem mieszania przez tabelę permutacji.

Drugim krokiem jest to, że sumując oktawy, będziesz chciał skalować okres z częstotliwością oktawy. Zasadniczo będziesz chciał, aby każda oktawa ułożyła cały obraz tylko jeden raz, a nie wiele razy:

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

Złóż to razem, a otrzymasz coś takiego:

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

Taflowy hałas fBm

Jak widać, to rzeczywiście kafelki płynnie:

fBm Hałas, kafelki

Z kilkoma drobnymi poprawkami i mapowaniem kolorów, oto obraz chmury w kafelkach 2x2:

Chmury

Mam nadzieję że to pomoże!

Boojum
źródło
3
nie jestem facetem pytona, więc pytam, jak x*2**oprzekonwertować na C? czy to jest: x*pow(2,o)czy pow(x*2,o)?
idev
7
x*pow(2, o), ponieważ potęgowanie ma wyższy priorytet niż mnożenie.
John Calsbeek,
1
czy ktoś mógłby przekonwertować to na C? mam ogromne problemy ze zrozumieniem tego kodu, ponieważ nigdy nie zrobiłem nic z Pythonem. na przykład czym jest awartość? i nie jestem pewien, w jaki sposób funkcje przekształcają się w C ... dostaję tylko proste linie wyjściowe.
idev
1
Jest to zdecydowanie najlepsze rozwiązanie, o ile nic nie przeszkadza w powiązaniu dziedziny hałasu z kształtem płytki. Na przykład nie pozwala to na dowolne obroty. Ale jeśli nie potrzebujesz czegoś takiego, jest to idealna odpowiedź.
John Calsbeek,
1
Uwaga: jeśli chcesz wygenerować inny rozmiar niż 128, NIE zmieniaj wartości liczbowych w wierszu im.putdata(data, 128, 128). (Dla tych, którzy nie znają pytona lub PIL: oznaczają skalę i przesunięcie, a nie rozmiar obrazu.)
Antti Kissaniemi,
87

Oto jeden dość sprytny sposób, który wykorzystuje szum 4D Perlin.

Zasadniczo zamapuj współrzędną X swojego piksela na okręgu 2D, a współrzędną Y swojego piksela na drugim okręgu 2D i umieść te dwa koła prostopadle względem siebie w przestrzeni 4D. Uzyskana tekstura jest taflowa, nie ma wyraźnych zniekształceń i nie powtarza się tak jak lustrzana tekstura.

Skopiuj i wklej kod z artykułu:

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
John Calsbeek
źródło
3
To zdecydowanie właściwa odpowiedź. Dodawanie wymiarów to stara sztuczka matematyki. Olinde Rodrigues docet (Sir WR Hamilton docet też, ale nieco mniej)
FxIII
@FxIII, czy wiesz, jak należy wdrożyć tę Noise4D ()? Chciałbym tego spróbować, ale nie mam pojęcia, jak ta funkcja Noise4D () powinna działać.
idev
4
Możesz użyć dowolnej funkcji szumu 4D. Hałas Simplex byłby moją rekomendacją. webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
John Calsbeek
2
dzięki John! sprawdziło się, słodko! nikt tego nie powiedział, ale: x1, y1, x2, y2 wydaje się być pewnego rodzaju skalowaniem, im większa odległość, tym bardziej szczegółowy hałas. jeśli to pomoże komukolwiek.
idev
5
Zauważ, że jest to topologicznie równoważne z odpowiedzią bobobobo: twoje mapowanie osadza 2 torus w ℝ⁴, co jest możliwe bez zniekształceń metrycznych, które nieuchronnie występują podczas osadzania go w ℝ³.
leftaroundabout
22

Ok, rozumiem. Odpowiedzią jest chodzenie torusem w szumie 3D, generując z niego teksturę 2D.

torus otacza 2 reż

Kod:

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

Wyniki:

Pewnego razu:

tilable sky

I kafelki:

pokazując kafelki

Bobobobo
źródło
6
To trochę działa, ale wygląda na to, że dostajesz sporo zniekształceń z powodu krzywizny torusa.
Nathan Reed
1
naprawdę możesz po prostu modulować pozycję, ale uwielbiam wszystkie niesamowite / kreatywne odpowiedzi na to pytanie. Tak wiele różnych sposobów na zrobienie tego samego.
zauważyłem, że tak naprawdę nie chcesz używać wartości 0-1, ale 0-0,9999 ... wartości! więc użyłbyś: x / szerokość, y / wysokość itp. w przeciwnym razie szwy nie pasują (sprawia, że ​​przeciwne krawędzie mają dokładnie te same piksele). wygląda również na to, że funkcja PerlinNoise3D () wymaga również zaciśnięcia wartości wyniku lub przepełnienia niektórych wartości pikseli.
idev
@Nathan, czy wiesz, jak naprawić zniekształcenie?
idev
2
@idev Uważam, że sposobem na usunięcie zniekształceń jest użycie metody 4D w górnej odpowiedzi na to pytanie. ;)
Nathan Reed
16

Jednym prostym sposobem, jaki mogę wymyślić, jest wzięcie wyjścia funkcji szumu i odbicie lustrzane / odwrócenie w obraz, który jest dwa razy większy. Trudno to wyjaśnić, więc oto obraz: wprowadź opis zdjęcia tutaj

Teraz, w tym przypadku, jest całkiem oczywiste, co zrobiłeś, kiedy na to spojrzałeś. Mogę wymyślić dwa sposoby (ewentualnie :-)) rozwiązania tego:

  1. Możesz zrobić ten większy obraz, a następnie wygenerować na nim trochę więcej szumu, ale (i nie jestem pewien, czy to możliwe) skupiony na środku (więc krawędzie pozostają takie same). Może dodać dodatkową różnicę, która sprawi, że twój mózg będzie myślał, że to nie tylko lustrzane odbicie.

  2. (Nie jestem również pewien, czy jest to możliwe). Możesz spróbować manipulować danymi wejściowymi do funkcji szumu, aby inaczej wygenerować obraz początkowy. Będziesz musiał to zrobić metodą prób i błędów, ale poszukaj funkcji, które przyciągają wzrok podczas układania go w kafelki / dublowania, a następnie spróbuj go nie generować.

Mam nadzieję że to pomoże.

Richard Marskell - Drackir
źródło
3
Bardzo fajnie, ale zbyt symetrycznie!
bobobobo
1
@obobobo Właśnie tak myślałem, że pozostałe kroki złagodzą. Tak więc możesz wygenerować „bazę” za pomocą tej metody, a następnie dodać więcej szczegółów do całej sprawy, aby wyglądała, jakby nie była (więc) dublowana.
Richard Marskell - Drackir
Kiedy robisz takie rzeczy, zaczynasz mieć dziwne wzorce. Ten w szczególności wygląda trochę jak motyl. Jednak proste rozwiązanie.
notlesh
To było moje pierwsze podejście, ale ma problem, widoczny tutaj: dl.dropbox.com/u/6620757/noise_seam.png Przekraczając granicę klapki, powodujesz rozłączenie funkcji hałasu poprzez natychmiastowe odwrócenie nachylenia funkcjonować. Nawet jeśli zastosujesz drugą funkcję szumu na górze, może ona nadal być widoczna na wyjściu.
Jherico
Świetny pomysł. Można to łatwo zrobić w module cieniującym pikseli za pomocą funkcji fali trójkąta :tex2d(abs(abs(uv.x)%2.0-1.0), abs(abs(uv.y)%2.0-1.0))
tigrou
10

Pierwsza wersja tej odpowiedzi była w rzeczywistości błędna, zaktualizowałem ją

Metodą, którą z powodzeniem zastosowałem, jest kafelkowanie domeny szumów . Innymi słowy, ustaw swoją noise2()funkcję podstawową jako okresową. Jeśli noise2()jest okresowy i betajest liczbą całkowitą, powstały hałas będzie miał taki sam okres jak noise2().

Jak możemy tworzyć noise2()okresowe? W większości implementacji funkcja ta wykorzystuje pewien rodzaj szumu sieci. Oznacza to, że pobiera losowe liczby o współrzędnych całkowitych i interpoluje je. Na przykład:

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

Tę funkcję można w prosty sposób zmodyfikować, aby stała się okresowa z kropką całkowitą. Po prostu dodaj jedną linię:

integer_X = integer_X % Period

przed obliczeniem v1i v2. W ten sposób wartości we współrzędnych całkowitych będą powtarzane co jednostki okresu, a interpolacja zapewni, że wynikowa funkcja będzie płynna.

Zauważ jednak, że działa to tylko wtedy, gdy Okres jest większy niż 1. Tak więc, aby faktycznie użyć tego do tworzenia bezszwowych tekstur, musisz spróbować kwadratu Okres x Okres, a nie 1x1.

Nieważne
źródło
Ale w jaki sposób tworzysz noise2okresowe (z krótkim okresem, takim jak 1 jednostka)? Myślę, że o to ostatecznie zadaje pytanie. Standardowy hałas Perlina jest okresowy z okresem 256 na każdej osi, ale potrzebny jest zmodyfikowany hałas o mniejszym okresie.
Nathan Reed,
@Nathan Reed Jeśli zadzwonisz noise2jak zasugerowano, to będzie uzyskać okresowe rezultaty, czy sama funkcja jest okresowa, czy nie. Ponieważ argumenty obejmują co 1 jednostkę.
Nevermind,
1
Ale wtedy dostajesz szwy na liniach siatki, prawda? Ponieważ nie ma gwarancji, że szum2 (0, 0,999) jest zbliżony do szumu2 (0, 0), chyba że coś przeoczyłem.
Nathan Reed,
1
@Nathan Reed To dobra uwaga. W rzeczywistości właśnie ponownie sprawdziłem mój stary kod i okazało się, że się myliłem. Przeredaguję teraz odpowiedź.
Nevermind
Wspaniały! To właściwie dobra odpowiedź. +1 :)
Nathan Reed
6

Inną alternatywą jest generowanie hałasu za pomocą bibliotek libnoise. Bezproblemowo możesz generować hałas na teoretycznej nieskończonej ilości miejsca.

Spójrz na następujące: http://libnoise.sourceforge.net/tutorials/tutorial3.html#tile

Istnieje również port XNA powyższego na: http://bigblackblock.com/tools/libnoisexna

Jeśli korzystasz z portu XNA, możesz zrobić coś takiego:

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 to funkcja do wywołania, aby uzyskać sekcje w każdym kierunku, które będą płynnie łączyć się z resztą tekstur.

Oczywiście ta metoda jest bardziej kosztowna niż posiadanie pojedynczej tekstury, którą można zastosować na wielu powierzchniach. Jeśli chcesz utworzyć losowe tekstury do układania w kafelki, może to Cię zainteresować.

jgallant
źródło
6

Chociaż istnieją tutaj odpowiedzi, które mogłyby zadziałać, większość z nich jest skomplikowana, powolna i problematyczna.

Wszystko, co naprawdę musisz zrobić, to skorzystać z funkcji okresowego generowania hałasu. Otóż ​​to!

Doskonałą implementację domeny publicznej opartą na „zaawansowanym” algorytmie hałasu Perlina można znaleźć tutaj . Potrzebna funkcja to pnoise2. Kod został napisany przez Stefana Gustavsona, który tutaj skomentował dokładnie tę kwestię i to, jak inni przyjęli niewłaściwe podejście. Posłuchaj Gustavsona, on wie o czym mówi.

W odniesieniu do różnych rzutów sferycznych niektórzy tutaj sugerują: cóż, w zasadzie działają (powoli), ale wytwarzają również teksturę 2D, która jest spłaszczoną kulą, tak że krawędzie byłyby bardziej skondensowane, prawdopodobnie powodując niepożądany efekt. Oczywiście, jeśli zamierzasz rzutować teksturę 2D na kulę, to jest właściwy sposób, ale nie o to proszono.

Tal Liron
źródło
4

Oto o wiele prostszy sposób robienia hałasu z płytek:

taflowy szum perlina z kodu shadertoy

Do każdej skali hałasu używasz modułowego zawinięcia. Pasują one do krawędzi obszaru bez względu na używaną skalę częstotliwości. Musisz więc używać normalnego szumu 2D, który jest znacznie szybszy. Oto kod WebGL na żywo, który można znaleźć w ShaderToy: https://www.shadertoy.com/view/4dlGW2

Trzy górne funkcje wykonują całą pracę, a fBM przechodzi wektor x / y w zakresie od 0,0 do 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);
}
Krondike
źródło
1
Twój link do obrazu zniknął. Zgadłem i zastąpiłem go zrzutem ekranu z kodu shadertoy, który opublikowałeś. Jeśli to nie jest poprawne, prześlij ponownie zamierzony obraz bezpośrednio na serwer Stack Exchange.
Pikalek
3

Miałem pewne niezłe wyniki interpolując w pobliżu krawędzi płytki (owinięte krawędzią), ale to zależy od tego, jaki efekt chcesz osiągnąć i od dokładnych parametrów hałasu. Działa świetnie w przypadku nieco rozmytego hałasu, nie tak dobrze w przypadku kolczastych / drobnoziarnistych.

kaoD
źródło
0

Sprawdzałem ten wątek w poszukiwaniu odpowiedzi na podobny problem, a następnie dostałem czyste i kompaktowe rozwiązanie od autora tego kodu python, aby generować szum fraktalny z szumu perlin / simplex. Zaktualizowany kod znajduje się w tym (zamkniętym) numerze i można go wznowić, ustawiając gradienty dla prawej strony „generatora” równe tym z lewej strony (i takie same dla góry i dołu), na przykład w

# 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]

Wygląda to na eleganckie i czyste rozwiązanie, unikam tutaj kopiowania całego kodu (ponieważ nie jest to moje własne rozwiązanie), ale jest on dostępny pod linkiem podanym powyżej. Mam nadzieję, że może to być przydatne dla kogoś, kto chce stworzyć obraz fraktalny 2d z kafelkami, taki jak ten, którego potrzebowałem, wolny od artefaktów i zniekształceń.

kafelkowy teren fraktalny

Stefano
źródło