Bruit aléatoire basé sur la graine

16

Je travaille actuellement sur un programme qui devrait générer du bruit aléatoire sur un écran basé sur les «coordonnées» d'un pixel. Les coordonnées doivent avoir la même couleur à chaque redémarrage du programme. Cependant, en utilisant l'utilitaire Java, les résultats que j'obtiens ne sont pas aussi aléatoires que je le souhaiterais:

capture d'écran

Je pensais que si j'utilisais les coordonnées combinées (comme dans un entier formé à partir des deux coordonnées l'une à côté de l'autre), chaque coordonnée aurait un nombre différent. En utilisant ce nombre comme graine, je m'attendais à obtenir un nombre aléatoire différent pour chaque coordonnée à utiliser pour la valeur RVB de cette coordonnée.

Voici le code que j'ai utilisé:

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

Le modèle créé par le programme est-il dû au fonctionnement de la fonction aléatoire de Java ou est-ce que je fais quelque chose de mal et dois-je essayer une approche différente?

Mise à jour: j'ai maintenant essayé de me débarrasser des problèmes liés à la concaténation en utilisant le code suivant:

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

D'une certaine manière, cela a également fourni (à mon avis) une image suffisamment aléatoire:

mew image

Ce code est cependant réensemencé trois fois par pixel. Même si ce n'est pas un problème pour moi en ce moment, j'envisage de changer ce code au cas où j'aurais besoin de meilleures performances plus tard.

libellule
la source
3
Je ne suis pas sûr de Java's Random mais je suis sûr que ce n'est pas vraiment aléatoire ... Lire en.wikipedia.org/wiki/Pseudorandom_number_generator Vous comprendrez pourquoi vous voyez ces modèles.
Salketer
23
Quelque chose de crucial qui manque dans les autres réponses: ne réamorcez pas le RNG pour chaque pixel. Semez-le une fois et générez des valeurs consécutives pour tous les pixels de votre image en fonction de cela.
Konrad Rudolph
4
Remarque: une génération de nombres pseudo-aléatoires peut être uniformément répartie dans une dimension , mais échoue lors de l'utilisation de plusieurs dimensions ... vous générez effectivement des points en 3D (r, g et b et 3 coordonnées différentes), vous avez donc besoin d'un générateur aléatoire qui garantit non seulement que les valeurs qu'il génère sont uniformément réparties, mais aussi les triplets qu'il génère sont uniformément répartis dans l'espace 3D.
Bakuriu
6
@Bakuriu Si X, Y et Z sont des variables aléatoires uniformes indépendantes, alors je suis presque sûr (X, Y, Z) est uniforme dans l'espace 3D.
Jack M
2
Vous pouvez expérimenter l'utilisation de différents RNG, comme le Mersenne Twister .
Kevin

Réponses:

21

La java.util.Randomclasse Java vous donne généralement des séquences de nombres pseudo-aléatoires qui sont suffisamment bonnes pour être utilisées dans les jeux 1 . Cependant, cette caractéristique ne s'applique qu'à une séquence de plusieurs échantillons basés sur une graine. Lorsque vous réinitialisez le RNG avec des valeurs de départ incrémentées et que vous ne regardez que la première valeur de chaque séquence, les caractéristiques de caractère aléatoire ne seront pas aussi bonnes.

Ce que vous pourriez faire à la place:

  • Utilisez la même graine pour générer des blocs entiers de pixels à la fois. Par exemple, lorsque vous avez besoin de la valeur de couleur du pixel 425: 487, introduisez les coordonnées 400: 400 dans le RNG, générez 10000 couleurs aléatoires et utilisez la couleur à l'index 2587 (25 * 100 + 87). Les blocs générés de cette manière doivent être mis en cache pour éviter de recréer ces 10 000 couleurs aléatoires pour chaque pixel de ce bloc.
  • Au lieu d'utiliser un générateur de nombres aléatoires, utilisez une fonction de résumé de message pour transformer une paire de coordonnées en une valeur de couleur. La sortie de la plupart des MDF est suffisamment imprévisible pour répondre à la plupart des tests d'aléatoire. La sortie est généralement supérieure au 24 bits dont vous avez besoin pour une valeur RVB, mais les tronquer n'est généralement pas un problème.

    Pour améliorer les performances, vous pouvez combiner la génération de résumé de message avec des morceaux. Générez de petits morceaux de pixels qui sont juste assez grands pour utiliser toute la longueur d'une sortie de votre fonction de résumé.

1 quand il est absolument essentiel que personne ne puisse prédire le nombre suivant, utilisez le plus lent mais moins prévisiblejava.security.SecureRandom

Philipp
la source
13

Les coordonnées doivent avoir la même couleur à chaque fois que vous redémarrez le programme

Dans ce cas, vous souhaiterez utiliser une fonction de bruit déterministe telle que le bruit Perlin ou le bruit simplex .

( Voir cette question pour plus d'informations sur le bruit Perlin avec de jolies images. )

Pour la plupart, l'utilisation d'une fonction intégrée random()ou similaire vous donnera des valeurs différentes à chaque fois que vous exécuterez le programme, car ils peuvent utiliser l'horloge comme entrée ou une autre valeur pseudo-aléatoire.

Une autre option consiste à générer une "carte de bruit" une fois, hors ligne, puis à l'utiliser comme source de nombre aléatoire plus tard.

Dans votre implémentation, vous concaténez les représentations de chaîne de x et y. C'est mauvais car ce n'est pas unique dans le domaine. Par exemple,

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

Bonne chance!

3Dave
la source
1
Bon point sur les nombres concaténés. Le programme donne cependant toujours le même résultat exact si je lance le programme plusieurs fois. J'ai également pris en compte le bruit perlin / simplex, je pourrais y jeter un œil et voir s'il fonctionne mieux. Je ne suis cependant pas encore sûr de la raison pour laquelle Java crée des modèles, car le problème de concaténation ne semble pas le résoudre entièrement
libellule
1
N'est-il pas suffisant de simplement semer Random avec une valeur de départ constante avant de générer les pixels?
Jack M
1
@JackM Cela dépend entièrement de l'algorithme PRNG en jeu.
3Dave
4
"J'ai vu une fois une implémentation rand () qui utilisait chaque valeur comme graine pour la valeur suivante." N'est-ce pas ainsi que fonctionnent la plupart des générateurs de nombres pseudo-aléatoires non cryptographiques? Ils utilisent le nombre aléatoire (ou état) précédent comme entrée pour générer le nombre / état aléatoire suivant.
JAB
2
@DavidLively Pratiquement tous les PRNGs font cela ou quelque chose d'équivalent, à moins que leur état interne soit plus grand que la plage de nombres qu'ils génèrent (par exemple les torsades de Mersenne), et même alors la séquence de nombres aléatoires est bien sûr entièrement déterminée par la graine.
Konrad Rudolph
9

Voyons ce que vous faites exactement:

  • Vous parcourez tous les pixels un par un
  • Pour chaque pixel, vous utilisez la concaténation de ses coordonnées comme graine
  • Vous commencez ensuite un nouveau hasard à partir de la graine donnée et retirez 3 numéros

Tout cela sonne bien, mais vous recevez un motif parce que:

Pixel à 1,11 et pixel à 11,1 sont tous deux ensemencés le nombre 111 de sorte qu'ils sont certains d'avoir la même couleur.

Aussi, tant que vous faites toujours le même cycle, vous ne pouvez utiliser qu'un seul générateur, pas besoin d'en utiliser un pour chaque pixel. Un pour l'image entière fera l'affaire! Il y aura toujours des schémas à cause du pseudo-aléatoire. @David_Lively a raison d'utiliser un algorithme de bruit, cela le rendra plus aléatoire.

Salketer
la source
Le fait est que la vue de l'image doit pouvoir se déplacer (en plus des coordonnées positives). Cette approche ne fonctionnera donc pas complètement
libellule
4
En fait, «tout cela» ne sonne pas bien - réamorcer un RNG déterministe pour chaque pixel est une stratégie terrible à moins que la graine elle-même ne provienne d'un RNG cryptographique (et même alors, c'est une stratégie bancale pour des raisons sans rapport avec la distribution).
Konrad Rudolph
vous pouvez inclure la manière correcte de concaténer les nombres dans ce contexte. C'est à dire. (largeur x + y *)
Taemyr
1

Faites un générateur de couleurs, puis produisez vos couleurs pour votre carreau. Semez une seule fois! Vous n'avez pas besoin de semer plus que cela, au moins par tuile.

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

Et l'utilisation sera comme suit:

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

Avec cela, si vous n'êtes pas satisfait du résultat, changez simplement de Randomgraine. De plus, il vous suffit de stocker / communiquer la graine et les tailles afin que tous les clients aient la même image.

Olivier Grégoire
la source
1

Au lieu d'utiliser Random, envisagez d'utiliser un condensé de hachage comme MD5. Il fournit une valeur «aléatoire» difficile à prévoir sur la base d'une certaine entrée, mais toujours la même valeur pour la même entrée.

Exemple:

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

REMARQUE: je ne sais pas d'où vient Color.rgb888 (..), donc je ne sais pas quelle est la plage autorisée. 0-255 est normal cependant.

Améliorations à considérer:

  • Créez des variables MessageDigest et ByteBuffer en dehors de la classe, pour améliorer les performances. Vous devrez réinitialiser le ByteBuffer pour ce faire, et la méthode ne sera plus sécurisée par les threads.
  • Le tableau de résumé contiendra les valeurs d'octets 0-255, si vous voulez d'autres plages, vous devrez faire quelques calculs sur elles.
  • Si vous voulez des résultats «aléatoires» différents, vous pouvez ajouter une sorte de «graine». Par exemple, passez à ByteBuffer.allocate (12) et ajoutez un .putInt (seed).
cranphin
la source
1

D'autres ont souligné qu'une façon d'obtenir le comportement souhaité consiste à utiliser une fonction de hachage, également appelée «fonction de résumé des messages». Le problème est que ceux-ci sont souvent basés sur des algorithmes comme MD5, qui est cryptographiquement sécurisé (c'est-à-dire vraiment, vraiment, vraiment aléatoire) mais très lent. Si vous utilisez une fonction de hachage cryptographique chaque fois que vous avez besoin d'un pixel aléatoire, vous rencontrerez des problèmes de performances assez graves.

Cependant, il existe des fonctions de hachage non cryptographiques qui peuvent produire des valeurs suffisamment aléatoires pour votre objectif tout en étant rapides. Celui que j'atteins habituellement est murmure . Je ne suis pas un utilisateur Java mais il semble qu'il y ait au moins une implémentation Java disponible. Si vous constatez que vous avez vraiment besoin que chaque pixel soit généré à partir de ses coordonnées, plutôt que de les générer tous en même temps et de les stocker dans une texture, ce serait une bonne façon de le faire.

Nathaniel
la source
1

J'utiliserais un nombre premier supérieur à 2000 (résolution typique maximale)
Cela minimisera (ou éliminera les graines en double)

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;
    }
}
paparazzo
la source
0

Randomest assez aléatoire. Vous l'utilisez mal pour deux raisons principales.

  • Il n'a pas été conçu pour être réensemencé à plusieurs reprises. Les propriétés aléatoires ne valent que pour une seule séquence de nombres aléatoires.
  • Il y a une énorme corrélation Integer.valueOf(Integer.toString(x)+Integer.toString(y))entre les pixels avec lesquels vous semez.

J'utiliserais simplement une variante du code suivant, où vous pouvez choisir une fonction de hachage (n'utilisez pas Integer.getHashCode) dans les réponses à /programming/9624963/java-simplest-integer- hacher

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

où la fonction de hachage pourrait être

Jimmy
la source
0

Vous pouvez essayer d'utiliser l'heure actuelle du système comme une graine comme ceci:

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

Espérons que cela produise une valeur plus aléatoire.

développeur anonyme
la source
Cependant, cela ne crée pas à chaque fois la valeur dame pour la coordonnée dame.
libellule
0

Voici une fonction de shader statique sur une ligne que j'ai trouvée - poltergeist (Noisy Ghost).

Il prend une coordonnée 2D et une graine, et s'affiche en monotone comme demandé. Il fonctionne à fps en temps réel, quelle que soit la résolution de l'écran. C'est à cela que servent les GPU.

// poltergeist (noisy ghost) pseudo-random noise generator function
// [email protected] 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)); 
}

N'importe quelle résolution, n'importe quelle texture, sur n'importe quel appareil (mobile aussi) prenant en charge GL (qui est à peu près n'importe quel écran).

Voyez-le courir ici, en ce moment!

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

Vous pouvez facilement inclure ce shader dans votre programme java en utilisant opengl standard, ou dans n'importe quel navigateur en utilisant webgl standard.

Juste pour le plaisir, je jette le gant pour que quiconque bat Poltergeist en qualité et en performance sur tous les appareils. Règles de Noisy Ghost! Invaincu!

Birkensocks
la source