Problem z szumem białym
Szum biały przypisuje każdemu punktowi w przestrzeni całkowicie niezależną wartość losową. Jego widmo mocy jest płaskie — wszystkie częstotliwości wnoszą jednakowy wkład. Naturalne tekstury są zupełnie inne: większość ich mocy przypada na niskie częstotliwości (cechy wielkoskalowe), a moc maleje przy wyższych częstotliwościach (drobne detale). Widmo mocy naturalnych tekstur przybliżenie podąża za wzorem P(f) ~ 1/f² — cechą charakterystyczną struktur fraktalnych, samopodobnych.
Interpolowany szum wartości — rozmieszczanie losowych wartości na siatce i interpolowanie między nimi — zapewnia spójność w skali siatki, ale wykazuje wyraźne artefakty wyrównane do siatki, gdy częstotliwość odpowiada rozstawowi siatki. Szum Perlina rozwiązuje ten problem, używając losowych gradientów zamiast losowych wartości.
Szum gradientowy: intuicja Perlina
Klasyczny szum Perlina przypisuje każdemu całkowitoliczbowemu punktowi siatki losowy jednostkowy wektor gradientu. Wartość szumu w punkcie P jest obliczana poprzez:
- Znalezienie otaczających narożników siatki (4 w 2D, 8 w 3D)
- Dla każdego narożnika obliczenie iloczynu skalarnego gradientu tego narożnika z wektorem od narożnika do P
- Płynną interpolację tych iloczynów skalarnych za pomocą funkcji wygładzającej (fade)
// Ulepszona funkcja wygładzająca (fade) Kena Perlina (2002)
// 6t^5 - 15t^4 + 10t^3 — zerowa pierwsza i druga pochodna w 0 i 1
function fade(t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
// Iloczyn skalarny gradientu z wektorem przesunięcia
function grad(hash, x, y) {
const h = hash & 3;
const u = h < 2 ? x : y;
const v = h < 2 ? y : x;
return ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
}
Funkcja wygładzająca jest kluczowa. Oryginalny szum Perlina wykorzystywał funkcję sześcienną 3t² − 2t³ (smoothstep), która ma zerową pierwszą pochodną w 0 i 1, ale niezerową drugą pochodną — co tworzyło widoczne nieciągłości na mapach normalnych i mapach wypukłości (bump maps). Funkcja piątego stopnia z 2002 roku, 6t⁵ − 15t⁴ + 10t³, ma zerową pierwszą i drugą pochodną na krańcach, zapewniając ciągłość klasy C² i eliminując te artefakty.
Użycie gradientów zamiast wartości eliminuje pasmowanie wyrównane do siatki, które dręczy szum wartości. Konstrukcja oparta na iloczynie skalarnym gwarantuje, że szum płynnie przechodzi przez zero w każdym punkcie siatki (ponieważ wektor przesunięcia ma tam zerową długość), tworząc przyjemną właściwość „przejść przez zero w węzłach kraty", która unika problemów obciążenia typowych dla interpolowanego szumu wartości.
Oktawy, persistence i szum fraktalny
Pojedyncza oktawa szumu Perlina ma jedną charakterystyczną częstotliwość — określoną przez rozstaw siatki. Aby stworzyć wieloskalową, fraktalnopodobną teksturę, sumujemy wiele oktaw o rosnącej częstotliwości i malejącej amplitudzie:
function fbm(x, y, octaves, persistence, lacunarity) {
let value = 0;
let amplitude = 1.0;
let frequency = 1.0;
let maxValue = 0;
for (let i = 0; i < octaves; i++) {
value += perlin(x * frequency, y * frequency) * amplitude;
maxValue += amplitude;
amplitude *= persistence; // zwykle 0.5
frequency *= lacunarity; // zwykle 2.0
}
return value / maxValue; // normalizacja do [-1, 1]
}
Przy persistence = 0,5 i lacunarity = 2,0 każda oktawa podwaja częstotliwość i zmniejsza amplitudę o połowę. Po N oktawach najdrobniejsze detale są 2^N razy mniejsze niż najgrubsze. Widmo mocy podąża za wzorem P(f) ~ f^(-2·log₂(1/persistence)) — dla persistence = 0,5 daje to P(f) ~ f⁻², co odpowiada statystykom naturalnych tekstur.
Zmiana persistence zmienia charakter tekstury: wysoka persistence (0,7–0,9) daje wyraziste, kontrastowe tekstury z wyeksponowanymi drobnymi detalami. Niska persistence (0,2–0,4) daje gładkie, łagodnie faliste powierzchnie, gdzie dominują grube cechy. Turbulencja wykorzystuje wartość bezwzględną każdej oktawy — |noise(x,y)| — tworząc ostre grzbiety i kształty przypominające kłębiące się chmury.
Szum simpleksowy: ulepszenie Perlina z 2001 roku
Szum simpleksowy Perlina z 2001 roku eliminuje dwie słabości oryginalnego algorytmu: złożoność obliczeniową (O(2^N) narożników siatki w N wymiarach staje się kosztowne dla 4D i więcej) oraz artefakty kierunkowe (siatka kwadratowa/sześcienna ma kierunki uprzywilejowane).
Szum simpleksowy wykorzystuje kratę simpleksową — najprostszy kształt geometryczny w N wymiarach. W 2D simpleksy to trójkąty równoboczne (3 narożniki) zamiast kwadratów (4 narożniki). W 3D — czworościany (4 narożniki) zamiast sześcianów (8 narożników). W N wymiarach — (N+1) narożników zamiast 2^N. Oszczędności złożoności dla szumu wysokowymiarowego są dramatyczne: 4D szum simpleksowy wymaga 5 obliczeń gradientu w porównaniu z 16 w oryginalnym algorytmie.
Krata simpleksowa ma też lepszą izotropię — odległość od dowolnego punktu wewnętrznego do najbliższego narożnika zmienia się mniej niż w kracie kwadratowej, co zmniejsza obciążenie kierunkowe wyniku. Szum simpleksowy stał się standardem dla wysokowydajnych shaderów GPU i silników gier.
Zastosowania: teren, chmury i systemy cząstek
Generowanie terenu nakłada oktawy z domain warping — podając wynik szumu z powrotem jako przesunięcie wejścia: height(p) = fbm(p + fbm(p)). Ten „zniekształcony" szum fraktalny tworzy meandrujące doliny i nawisające urwiska charakterystyczne dla realistycznych gór. Symulacja erozji (hydraulicznej i termicznej) dodatkowo dopracowuje teren, przenosząc materiał z miejsc o wysokim gradiencie do miejsc o niskim gradiencie, wyostrzając grzbiety i wygładzając doliny.
Animowany szum napędza wiele efektów w symulacjach na tej stronie. Turbulencja płynu wykorzystuje szum 3D lub 4D (dodając czas jako wymiar), by generować przestrzennie spójne zaburzenia prędkości. Przemieszczanie ziaren piasku wykorzystuje szum 2D przewijany w kierunku wiatru. Efekt zorzy polarnej wykorzystuje szum ze zniekształceniem dziedziny (domain warping) dla charakterystycznych kształtów kurtyn.