2013-08-15 17 views
5

Badam kilka różnych implementacji dla spójnego szumu (wiem, że istnieją biblioteki, ale to głównie dla mojego własnego zbudowania i ciekawości) i jak możesz z niego korzystać, i jest jeden problem Mam z oryginalnym szumem Perlina.Zasięg wyjściowy szumu Perlina

Zgodnie z , zakres wyjściowy będzie wynosił od -1 i 1, ale nie rozumiem, jak wartość ma być w tym zakresie.

Jak rozumiem, algorytm jest w zasadzie taki: każdy punkt siatki ma powiązany losowy wektor gradientu o długości 1. Następnie, dla każdego punktu, dla wszystkich czterech otaczających punktów siatki, obliczysz iloczyn skalarny gradientu losowego i wektora przechodzącego z tego punktu siatki. Następnie użyjesz fantazyjnej krzywej łatwości i interpolacji liniowej, aby obniżyć ją do jednej wartości.

Ale, tu jest mój problem: te produkty z wykresem czasami są poza zasięgiem [-1, 1], a ponieważ dokonujesz interpolacji liniowej ostatecznie pomiędzy produktami kropkowymi, nie oznacza to, że ostateczna wartość będzie, czasami poza zasięgiem [-1, 1]?

Załóżmy na przykład, że jeden z losowych wektorów jest (sqrt(2)/2, sqrt(2)/2) (który ma długość 1) oraz (0.8, 0.8) (który jest na placu jednostki), można uzyskać wynik około 1.131. Jeśli ta wartość jest używana w interpolacji liniowej, jest całkowicie możliwe, że wygenerowana wartość będzie większa niż 1. I rzeczywiście, z moją prostą implementacją zdarza się to dość często.

Czy tu czegoś brakuje?

Dla odniesienia, oto mój kod w Javie. Vec to prosta klasa do wykonania prostej arytmetyki wektorowej 2d, fade() jest krzywa łatwości, lerp() jest interpolacją liniową, a gradient(x, y) podaje gradient dla tego punktu siatki jako Vec. Zmienna gridSize daje rozmiar siatki w pikselach (ma typ double):

public double getPoint(int x, int y) { 
    Vec p = new Vec(x/gridSize, y/gridSize); 
    Vec d = new Vec(Math.floor(p.x), Math.floor(p.y)); 


    int x0 = (int)d.x, 
     y0 = (int)d.x; 


    double d00 = gradient(x0 , y0 ).dot(p.sub(x0 , y0 )), 
      d01 = gradient(x0 , y0 + 1).dot(p.sub(x0 , y0 + 1)), 
      d10 = gradient(x0 + 1, y0 ).dot(p.sub(x0 + 1, y0 )), 
      d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1)); 

    double fadeX = fade(p.x - d.x), 
      fadeY = fade(p.y - d.y); 

    double i1 = lerp(fadeX, d00, d10), 
      i2 = lerp(fadeX, d01, d11); 

    return lerp(fadeY, i1, i2); 
} 

Edycja: Oto kod do generowania losowych gradientów:

double theta = gen.nextDouble() * 2 * Math.PI; 
gradients[i] = new Vec(Math.cos(theta), Math.sin(theta)); 

Gdzie gen jest java.util.Random.

Odpowiedz

6

Masz y0 = (int)d.x;, ale masz na myśli d.y. Z pewnością wpłynie to na twój zakres wyjściowy i jest powodem, dla którego widzisz tak duże wartości poza zakresem.


Mimo to, w zakresie mocy od Perlin szumu jest nie faktycznie [1, 1]. Chociaż sam nie jestem do końca pewny tej matematyki (muszę się starzeć), this rather lengthy discussion wylicza, że ​​rzeczywisty zakres to [-sqrt (n)/2, sqrt (n)/2], gdzie n jest wymiarowość (2 w twoim przypadku). Dlatego zakres wyjściowy funkcji szumu 2D Perlin powinien wynosić [-0.707, 0.707]. Jest to w jakiś sposób związane z tym, że zarówno d, jak i parametry interpolacji są funkcją p.Jeśli przeczytasz tę dyskusję, możesz znaleźć dokładne wyjaśnienie, którego szukasz (szczególnie: post #7).

jestem testowania wdrożenia stosując następujący program (który ja hacked razem ze swoim przykładzie, więc przepraszam za dziwne użycie gridCells i gridSize):

import java.util.Random; 


public class Perlin { 

    static final int gridSize = 200; 
    static final int gridCells = 20; 
    static final Vec[][] gradients = new Vec[gridCells + 1][gridCells + 1]; 

    static void initializeGradient() { 
     Random rand = new Random(); 
     for (int r = 0; r < gridCells + 1; ++ r) { 
      for (int c = 0; c < gridCells + 1; ++ c) { 
       double theta = rand.nextFloat() * Math.PI; 
       gradients[c][r] = new Vec(Math.cos(theta), Math.sin(theta));     
      } 
     } 
    } 

    static class Vec { 
     double x; 
     double y; 
     Vec (double x, double y) { this.x = x; this.y = y; } 
     double dot (Vec v) { return x * v.x + y * v.y; } 
     Vec sub (double x, double y) { return new Vec(this.x - x, this.y - y); } 
    } 

    static double fade (double v) { 
     // easing doesn't matter for range sample test. 
     // v = 3 * v * v - 2 * v * v * v; 
     return v; 
    } 

    static double lerp (double p, double a, double b) { 
     return (b - a) * p + a; 
    } 

    static Vec gradient (int c, int r) { 
     return gradients[c][r]; 
    } 

    // your function, with y0 fixed. note my gridSize is not a double like yours.  
    public static double getPoint(int x, int y) { 

     Vec p = new Vec(x/(double)gridSize, y/(double)gridSize); 
     Vec d = new Vec(Math.floor(p.x), Math.floor(p.y)); 

     int x0 = (int)d.x, 
      y0 = (int)d.y; 

     double d00 = gradient(x0 , y0 ).dot(p.sub(x0 , y0 )), 
       d01 = gradient(x0 , y0 + 1).dot(p.sub(x0 , y0 + 1)), 
       d10 = gradient(x0 + 1, y0 ).dot(p.sub(x0 + 1, y0 )), 
       d11 = gradient(x0 + 1, y0 + 1).dot(p.sub(x0 + 1, y0 + 1)); 

     double fadeX = fade(p.x - d.x), 
       fadeY = fade(p.y - d.y); 

     double i1 = lerp(fadeX, d00, d10), 
       i2 = lerp(fadeX, d01, d11); 

     return lerp(fadeY, i1, i2); 

    } 

    public static void main (String[] args) { 

     // loop forever, regenerating gradients and resampling for range. 
     while (true) { 

      initializeGradient(); 

      double minz = 0, maxz = 0; 

      for (int x = 0; x < gridSize * gridCells; ++ x) { 
       for (int y = 0; y < gridSize * gridCells; ++ y) { 
        double z = getPoint(x, y); 
        if (z < minz) 
         minz = z; 
        else if (z > maxz) 
         maxz = z; 
       } 
      } 

      System.out.println(minz + " " + maxz); 

     } 

    } 

} 

widzę wartości w teoretycznym zakresie [-0.707, 0.707], chociaż ogólnie widzę wartości między -0,6 a 0,6; co może być konsekwencją rozkładu wartości i niskiej częstotliwości próbkowania.

+0

Dzięki za pomoc! To naprawiło to. – Oskar

0

Podczas obliczania iloczynu punktowego można uzyskać wartości poza zakresem -1 +1, jednak podczas etapu interpolacji wartość końcowa spada w zakresie -1 +1. Dzieje się tak dlatego, że wektory odległości produktów kropek, które są interpolowane, wskazują na przeciwne kierunki interpolowanej osi. Podczas ostatniej interpolacji wyjście nie przekroczy zakresu -1 +1.

Końcowy zakres wyjściowy szumu Perlin jest określony przez długość wektorów gradientowych. Jeśli mówimy o szumie 2D i naszym celu, aby uzyskać zakres wyjściowy -1 +1, długość wektorów gradientowych powinna wynosić sqrt (2) (~ 1,4142). Powszechnym błędem jest mieszanie tych wektorów (1, 1) (-1, 1) (1, -1) (-1, -1) i (1, 0) (0, 1) (-1, 0) (0, -1). W tym przypadku końcowy zakres wyjściowy nadal będzie wynosił -1 +1, jednak wartości w zakresie -0,707 +0,707 będą częstsze. Aby uniknąć tego problemu (1, 0) (0, 1) (-1, 0) (0, -1) wektory powinny zostać zastąpione przez (sqrt (2), 0) (0, sqrt (2)) (-sqrt (2), 0) (0, -sqrt (2)).