Losowy hałas na podstawie nasion


16

Obecnie pracuję nad programem, który powinien generować losowy szum na ekranie na podstawie „współrzędnych” piksela. Współrzędne powinny mieć ten sam kolor przy każdym ponownym uruchomieniu programu. Jednak korzystając z Java.Random, wyniki, które otrzymuję, nie są tak losowe, jak bym chciał:

zrzut ekranu

Pomyślałem, że jeśli użyję połączonych współrzędnych (jak w jednej liczbie całkowitej utworzonej z obu współrzędnych obok siebie), każda ze współrzędnych będzie miała inną liczbę. Korzystając z tej liczby jako zarodka, spodziewałem się uzyskać różne liczby losowe dla każdej współrzędnej do użycia dla wartości rgb tej współrzędnej.

Oto kod, którego użyłem:

public class Generate {

static Random Random;

    public static int TileColor(int x, int y){          
        Random = new Random(Integer.valueOf(Integer.toString(x)+Integer.toString(y)));
        int b = 1 + Random.nextInt(50);
        int g = 1 + Random.nextInt(50);
        int r = 1 + Random.nextInt(50);
        int color = -Color.rgb888(r, g, b);
        return color;
    }
}

Czy wzorzec, który program tworzy, wynika ze sposobu, w jaki działa funkcja Random w Javie, czy robię coś źle i czy powinienem spróbować zastosować inne podejście?

Aktualizacja: próbowałem teraz pozbyć się problemów związanych z konkatenacją, używając następującego kodu:

public static int TileColor(int x, int y){  
            Randomy = new Random(y);
            Randomx = new Random(x);
            Random = new Random(Integer.valueOf(Integer.toString(Randomx.nextInt(1234))+Integer.toString(Randomy.nextInt(1234))));
            int b = 1 + Random.nextInt(100);
            int g = 1 + Random.nextInt(100);
            int r = 1 + Random.nextInt(100);
            int color = -Color.rgb888(r, g, b);
            return color;
}

W jakiś sposób zapewniło to (moim zdaniem) wystarczająco losowy obraz:

obraz mew

Ten kod jest jednak resetowany trzy razy na piksel. Mimo że obecnie nie jest to dla mnie problemem, rozważam zmianę tego kodu na wypadek, gdyby później potrzebowałem lepszej wydajności.


3
Nie jestem pewien co do losowości Javy, ale jestem pewien, że nie jest tak naprawdę losowy ... Czytaj pl.wikipedia.org/wiki/Pseudorandom_number_generator Zrozumiesz, dlaczego widzisz te wzorce.
Salketer

23
Coś kluczowego, czego brakuje w innych odpowiedziach: nie resetuj RNG dla każdego piksela. Zasiej go raz i na tej podstawie wygeneruj kolejne wartości dla wszystkich pikseli obrazu.
Konrad Rudolph

4
Uwaga: generowanie liczb pseudolosowych może być równomiernie rozmieszczone w jednym wymiarze , ale zawodzi przy użyciu więcej niż jednego wymiaru ... skutecznie generujesz punkty w 3D (r, gib oraz 3 różne współrzędne), więc potrzebujesz generatora losowego, który gwarantuje nie tylko, że generowane przez niego wartości są równomiernie rozmieszczone, ale także tryplety, które generuje, są równomiernie rozmieszczone w przestrzeni 3D.
Bakuriu

6
@ Bakuriu Jeśli X, Y i Z są niezależnymi zmiennymi losowymi, to jestem całkiem pewien (X, Y, Z) jest jednolity w przestrzeni 3D.
Jack M

2
Możesz eksperymentować z użyciem różnych RNG, takich jak Twister Mersenne .
Kevin

Odpowiedzi:


21

java.util.RandomKlasa Javy zwykle daje ci ciąg liczb pseudolosowych, które są wystarczająco dobre do użycia w grach 1 . Jednak ta cecha dotyczy tylko sekwencji wielu próbek opartych na nasionach. Gdy ponownie zainicjujesz RNG z rosnącymi wartościami początkowymi i spojrzysz tylko na pierwszą wartość każdej sekwencji, charakterystyka losowości nie będzie prawie tak dobra.

Co możesz zamiast tego zrobić:

  • Użyj tego samego ziarna do generowania całych kawałków pikseli na raz. Na przykład, gdy potrzebujesz wartości koloru piksela 425: 487, podaj współrzędne 400: 400 do RNG, wygeneruj 10000 losowych kolorów i użyj koloru o indeksie 2587 (25 * 100 + 87). Tak wygenerowane fragmenty należy buforować, aby uniknąć ponownego generowania 10000 losowych kolorów dla każdego pojedynczego piksela tego fragmentu.
  • Zamiast używać generatora liczb losowych, użyj funkcji podsumowania wiadomości, aby zamienić parę współrzędnych na wartość koloru. Dane wyjściowe większości MDF są wystarczająco nieprzewidywalne, aby spełnić większość testów losowości. Dane wyjściowe są zwykle większe niż 24-bitowe wartości RGB, ale ich obcięcie zwykle nie stanowi problemu.

    Aby poprawić wydajność, możesz połączyć generowanie podsumowania wiadomości z fragmentami. Generuj małe fragmenty pikseli, które są wystarczająco duże, aby wykorzystać pełną długość jednego wyjścia funkcji skrótu.

1, gdy absolutnie niezbędne jest, aby nikt nie mógł przewidzieć następnej liczby, użyj wolniejszej, ale mniej przewidywalnejjava.security.SecureRandom


13

Współrzędne powinny mieć ten sam kolor przy każdym ponownym uruchomieniu programu

W takim przypadku należy użyć deterministycznej funkcji szumu, takiej jak szum Perlina lub hałas jednostronny .

( Zobacz to pytanie, aby uzyskać więcej informacji na temat hałasu Perlina z kilkoma ładnymi zdjęciami. )

W większości przypadków użycie wbudowanej random()lub podobnej funkcji daje różne wartości przy każdym uruchomieniu programu, ponieważ mogą one wykorzystywać zegar jako dane wejściowe lub inne wartości pseudolosowe.

Inną opcją jest wygenerowanie „mapy hałasu” raz, offline, a następnie wykorzystanie jej jako źródła liczb losowych w późniejszym terminie.

W swojej implementacji łączysz reprezentacje ciągów x i y. To źle, ponieważ nie jest unikalne w całej domenie. Na przykład,

x    y   concatenated
40   20  4020
402   0  4020
10   10  1010
101   0  1010
12   34  1234
123   4  1234
1   234  1234

Powodzenia!


1
Dobra uwaga na temat połączonych liczb. Program zawsze daje jednak ten sam wynik, jeśli uruchomię go wiele razy. Rozważyłem również hałas perlin / simplex, może zajrzę do tego i zobaczę, czy to zadziała lepiej. Nie jestem jednak jeszcze pewien, dlaczego Java tworzy wzorce, ponieważ problem konkatenacji nie rozwiązuje go całkowicie
ważka

1
Czy nie wystarczy po prostu zaszczepić Random stałą wartością ziarna przed wygenerowaniem pikseli?
Jack M

1
@JackM To zależy całkowicie od algorytmu PRNG w grze.
3Dave

4
„Kiedyś widziałem implementację rand (), która używała każdej wartości jako zarodka dla następnej wartości.” Czy nie tak działa większość niekryptograficznych generatorów liczb pseudolosowych? Używają poprzedniej liczby losowej (lub stanu) jako danych wejściowych do generowania następnej liczby losowej / stanu.
JAB

2
@DavidLively Praktycznie wszystkie PRNG robią to lub coś równoważnego, chyba że ich stan wewnętrzny jest większy niż zakres generowanych liczb (np. Twistery Mersenne), a nawet wtedy sekwencja liczb losowych jest oczywiście całkowicie determinowana przez ziarno.
Konrad Rudolph,

9

Zobaczmy, co dokładnie robisz:

  • Pętla przechodzi przez wszystkie piksele jeden po drugim
  • Dla każdego piksela używasz konkatenacji jego współrzędnych jako ziarna
  • Następnie zaczynasz nową losowość z danego ziarna i wyjmujesz 3 liczby

Wszystko to brzmi dobrze, ale otrzymujesz wzór, ponieważ:

Piksel przy 1,11 i piksel przy 11,1 są zaszczepione liczbą 111, więc na pewno będą miały ten sam kolor.

Ponadto, o ile zawsze wykonujesz cykl w ten sam sposób, możesz używać tylko jednego generatora, nie musisz używać jednego dla każdego piksela. Wystarczy jeden na cały obraz! Nadal będą jakieś wzorce z powodu pseudolosowości. @David_Lively ma rację, używając algorytmu Noise, dzięki czemu będzie wyglądał bardziej losowo.


Chodzi o to, że widok obrazu powinien móc się przesuwać (dalej do współrzędnych dodatnich). Więc to podejście nie zadziała całkowicie
ważka

4
W rzeczywistości „wszystko to” nie brzmi dobrze - ponowne ustawienie deterministycznego RNG dla każdego piksela jest okropną strategią, chyba że samo ziarno pochodzi z kryptograficznego RNG (a nawet wtedy jest to chwiejna strategia z powodów niezwiązanych z dystrybucją).
Konrad Rudolph

w tym kontekście możesz podać prawidłowy sposób konkatentacji liczb. To znaczy. (x + y * szerokość)
Taemyr

1

Stwórz generator kolorów, a następnie utwórz kolory dla swojej płytki. Nasiona tylko raz! Nie musisz wysiewać więcej, przynajmniej na płytkę.

public class RandomColorGenerator {
  private final int minValue;
  private final int range;
  private final Random random;
  public RandomColorGenerator(int minValue, int maxValue, Random random) {
    if (minValue > maxValue || (long)maxValue - (long)minValue > (long)Integer.MAX_VALUE) {
      throw new IllegalArgumentException();
    }
    this.minValue = minValue;
    this.range = maxValue - minValue + 1;
    this.random = Objects.requireNonNull(random);
  }

  public int nextColor() {
    int r = minValue + random.nextInt(range);
    int g = minValue + random.nextInt(range);
    int b = minValue + random.nextInt(range);
    return -Color.rgb888(r, g, b);
  }
}

public class Tile {
  private final int[][] colors;
  public Tile(int width, int height, RandomColorGenerator colorGenerator) {
    this.colors = new int[width][height];
    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        this.colors[x][y] = colorGenerator.nextColor();
      }
    }
  }

  public int getColor(int x, int y) {
    return colors[x][y];
  }
}

A użycie będzie następujące:

RandomColorGenerator generator = new RandomColorGenerator(1, 100, new Random(0xcafebabe));
Tile tile = new Tile(300, 200, generator);
...
// getting the color for x, y:
tile.getColor(x, y);

Dzięki temu, jeśli nie jesteś zadowolony z wyniku, po prostu zmień Randomziarno. Musisz tylko przechowywać / komunikować ziarno i rozmiary, aby wszyscy klienci mieli ten sam obraz.


1

Zamiast używać Random, rozważ użycie skrótu skrótu, takiego jak MD5. Zapewnia trudną do przewidzenia „losową” wartość na podstawie określonego wejścia, ale zawsze taką samą wartość dla tego samego wejścia.

Przykład:

public static int TileColor(int x, int y){
        final MessageDigest md = MessageDigest.getInstance("MD5");
        final ByteBuffer b = ByteBuffer.allocate(8);
        b.putInt(x).putInt(y);
        final byte[] digest = md.digest(b.array());
        return -Color.rgb888(digest[0], digest[1], digest[2]);
}

UWAGA: Nie wiem, skąd pochodzi Color.rgb888 (..), więc nie wiem, jaki jest dozwolony zakres. 0-255 jest jednak normalne.

Ulepszenia do rozważenia:

  • Zmień zmienne MessageDigest i ByteBuffer poza klasę, aby poprawić wydajność. Aby to zrobić, musisz zresetować ByteBuffer, a metoda nie będzie już bezpieczna dla wątków.
  • Tablica skrótów będzie zawierać wartości bajtów 0–255, jeśli chcesz mieć inne zakresy, musisz wykonać na nich matematykę.
  • Jeśli chcesz mieć inne „losowe” wyniki, możesz dodać coś w rodzaju „nasion”. Na przykład zmień na ByteBuffer.allocate (12) i dodaj .putInt (seed).

1

Inni zauważyli, że jednym ze sposobów uzyskania pożądanego zachowania jest użycie funkcji skrótu, czyli „funkcji podsumowania wiadomości”. Problem polega na tym, że często są one oparte na algorytmach takich jak MD5, który jest kryptograficznie bezpieczny (tj. Naprawdę, naprawdę, naprawdę losowy), ale bardzo powolny. Jeśli używasz kryptograficznej funkcji skrótu za każdym razem, gdy potrzebujesz losowego piksela, napotkasz dość poważne problemy z wydajnością.

Istnieją jednak niekryptograficzne funkcje skrótu, które mogą generować wartości, które są wystarczająco losowe dla twojego celu, a jednocześnie szybkie. Zwykle sięgam po szmer . Nie jestem użytkownikiem Java, ale wygląda na to, że dostępna jest co najmniej jedna implementacja Java . Jeśli okaże się, że naprawdę musisz wygenerować każdy piksel ze współrzędnych, zamiast generować je wszystkie jednocześnie i przechowywać je w teksturze, byłby to dobry sposób na zrobienie tego.


1

Użyłbym liczby pierwszej ponad 2000 (maksymalna typowa rozdzielczość).
To zminimalizuje (lub wyeliminuje zduplikowane nasiona)

public class Generate {

    static Random Random;

    public static int TileColor(int x, int y){          
        Random = new Random(x + 2213 * y);
        int b = 1 + Random.nextInt(50);
        int g = 1 + Random.nextInt(50);
        int r = 1 + Random.nextInt(50);
        int color = -Color.rgb888(r, g, b);
        return color;
    }
}

0

Randomjest wystarczająco losowy. Używasz go źle z dwóch głównych powodów.

  • Nie został zaprojektowany do wielokrotnego ponownego wysiewu. Losowe właściwości dotyczą tylko jednej sekwencji liczb losowych.
  • Istnieje ogromna korelacja Integer.valueOf(Integer.toString(x)+Integer.toString(y))między pikselami, które wysiewasz.

Chciałbym po prostu użyć pewnej odmiany następującego kodu, w którym możesz wybrać funkcję skrótu (nie używaj Integer.getHashCode) z odpowiedzi na /programming/9624963/java-simplest-integer- haszysz

public static int TileColor(int x, int y) {
    return hash(x ^ hash(y));
}

gdzie może być funkcja skrótu


0

Możesz spróbować użyć aktualnego czasu systemowego jako źródła:

Random random = new Random(System.currentTimeMillis())

Mam nadzieję, że przyniesie bardziej losową wartość.


Nie tworzy to jednak wartości dame dla współrzędnej dame za każdym razem.
ważka

0

Oto wymyślona przeze mnie liniowa funkcja cieniowania statycznego - poltergeist (Noisy Ghost).

Pobiera współrzędną 2D i ziarno i renderuje monotonicznie zgodnie z żądaniem. Działa w czasie rzeczywistym fps, niezależnie od rozdzielczości ekranu. Po to są GPU.

// poltergeist (noisy ghost) pseudo-random noise generator function
// dominic.cerisano@standard3d.com 03/24/2015

precision highp float;

float poltergeist(in vec2 coordinate, in float seed) 
{
    return fract(sin(dot(coordinate*seed, vec2(12.9898, 78.233)))*43758.5453); 
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) 
{   
    fragColor = vec4(poltergeist(fragCoord, iGlobalTime)); 
}

Dowolna rozdzielczość, dowolna tekstura, na dowolnym urządzeniu (również mobilnym) obsługującym GL (który jest praktycznie dowolny z ekranem).

Zobacz, jak działa tutaj, właśnie teraz!

https://www.shadertoy.com/view/ltB3zD

Możesz łatwo włączyć ten moduł cieniujący do programu Java za pomocą standardowego OpenGL lub w dowolnej przeglądarce za pomocą standardowego webgl.

Dla zabawy rzucam rękawicę, aby każdy mógł pokonać Poltergeist pod względem jakości i wydajności na wszystkich urządzeniach. Noisy Ghost rządzi! Niepokonany!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.