基于种子的随机噪声


16

我目前正在开发一个程序,该程序应基于像素的“坐标”在屏幕上生成随机噪声。每次重新启动程序时,坐标应具有相同的颜色。但是,使用Java的util.Random,我得到的结果并不像我想要的那样随机:

打印屏幕

我以为如果使用组合坐标(例如,由两个坐标彼此相邻形成的整数),则每个坐标将具有不同的数字。通过使用该数字作为种子,我希望为每个坐标获得一个不同的随机数,以用于该坐标的rgb值。

这是我使用的代码:

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

程序创建的模式是由于Java的Random函数的工作方式还是我做错了,应该尝试其他方法吗?

更新: 现在,我尝试通过使用以下代码来摆脱有关串联的问题:

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

以某种方式,这也提供了(我认为)足够随机的图像:

喵喵图像

但是,此代码每个像素确实重新设置了3次。即使这对我来说现在不是问题,但我确实考虑更改此代码,以防以后需要更好的性能。


3
不知道Java的Random,但是我很确定它不是真正的Random ...阅读en.wikipedia.org/wiki/Pseudorandom_number_generator 您将了解为什么会看到这些模式。
Salketer '16

23
其他答案中缺少的关键要素:不要为每个像素重新设置RNG。播种一次,然后基于该值为图像中的所有像素生成连续值。
康拉德·鲁道夫

4
注意:伪随机数生成可能在一个维度上均匀分布,但是在使用多个维度时会失败...您正在有效地生成3D点(r,g和b以及3个不同的坐标),因此您需要一个随机生成器不仅保证其生成的值均匀分布,而且还保证其生成的三元组在3D空间中均匀分布。
巴库里

6
@Bakuriu如果X,Y和Z是独立的均匀随机变量,那么我很确定(X,Y,Z)在3d空间中是均匀的。
杰克M

2
您可以尝试使用不同的RNG,例如Mersenne Twister
凯文

Answers:


21

Java的java.util.Random类通常会为您提供足以在游戏1中使用的伪随机数序列。但是,该特征仅适用于基于种子的多个样本序列。当您使用递增的种子值重新初始化RNG并仅查看每个序列的第一个值时,随机性特性将不会那么好。

您可以做什么:

  • 使用相同的种子一次生成整个像素块。例如,当您需要像素425:487的颜色值时,将坐标400:400输入RNG,生成10000个随机颜色,并使用索引2587(25 * 100 + 87)的颜色。应该缓存以这种方式生成的块,以避免为该块的每个像素重新生成10000种随机颜色。
  • 代替使用随机数生成器,可以使用消息摘要功能将坐标对转换为颜色值。大多数MDF的输出是不可预测的,足以满足大多数随机性测试。输出通常比RGB值所需的24位要多,但是将它们截断通常没有问题。

    为了提高性能,可以将消息摘要生成与块结合使用。生成小像素块,这些像素块恰好足以使用摘要功能的一个输出的全长。

1 当绝对没有人可以预测下一个数字时,请使用速度较慢但难以预测的数字java.security.SecureRandom


13

每次重新启动程序时,坐标都应具有相同的颜色

在这种情况下,您将需要使用确定性噪声函数,例如 Perlin噪声单纯形噪声

有关此问题的更多信息,请参阅此问题,并提供一些漂亮的图片。

在大多数情况下,random()每次运行程序时,使用内置函数或类似函数都会为您提供不同的值,因为它们可能将时钟用作输入或其他一些伪随机值。

另一种选择是离线生成一次“噪声图”,然后稍后将其用作您的随机数源。

在实现中,您要串联x和的字符串表示形式y。这很糟糕,因为它在整个域中不是唯一的。例如,

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

祝好运!


1
关于级联数字的要点。但是,如果我多次运行该程序,该程序始终会给出完全相同的结果。我也考虑过perlin / simplex噪声,请仔细研究一下,看看效果是否更好。但是我不确定为什么Java会创建模式,因为连接问题似乎无法完全解决
蜻蜓

1
在生成像素之前简单地用恒定的种子值对Random进行播种还不够吗?
杰克M

1
@JackM这完全取决于正在播放的PRNG算法。
3Dave

4
“我曾经看到rand()实现,该实现使用每个值作为下一个值的种子。” 这不是大多数非加密伪随机数生成器如何工作的吗?他们使用前一个随机数(或状态)作为输入来生成下一个随机数/状态。
JAB

2
@DavidLively 实际上,所有 PRNG都会执行此操作或进行等效操作,除非它们的内部状态大于它们生成的数字范围(例如,梅森捻线器),并且即使如此,随机数的顺序当然也完全由种子决定。
康拉德·鲁道夫

9

让我们看看您正在做什么:

  • 您一个接一个地遍历所有像素
  • 对于每个像素,将其坐标的级联用作种子
  • 然后,您从给定的种子开始一个新的随机数,并取出3个数字

所有这些听起来都不错,但您收到一个模式是因为:

1,11像素和11,1像素都以数字111为种子,因此它们肯定具有相同的颜色。

另外,只要您始终以相同的方式循环,就只能使用一个生成器,而无需为每个像素使用一个。一整个形象就可以了!由于伪随机性,仍然会有某种模式。@David_Lively关于使用一些噪声算法是正确的,它将使它看起来更加随机。


问题是图像的视图应该能够移动(进一步移至正坐标)。所以这种方法无法完全起作用
蜻蜓

4
实际上,“所有这一切” 听起来都不是一件好事-为每个像素重新提供确定性RNG是一个糟糕的策略,除非种子本身来自加密RNG(即使这样,由于与分发无关的原因,这也是一个很奇怪的策略)。
康拉德·鲁道夫

您可能会在这种情况下包括正确的数字组合方式。就是 (x + y * width)
Taemyr

1

制作颜色生成器,然后为瓷砖生成颜色。只播一次!至少每个磁贴,您不需要播种更多的种子。

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

用法如下:

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

如果您对结果不满意,只需更改Random种子即可。另外,您只需要存储/传达种子和大小,以便所有客户端具有相同的图像。


1

与其使用Random,不如考虑使用MD5之类的哈希摘要。它难以根据某个输入来预测“随机”值,但是对于同一输入始终提供相同的值。

例:

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

注意:我不知道Color.rgb888(..)的来源,所以我不知道允许的范围是多少。0-255是正常的。

需要考虑的改进:

  • 将MessageDigest和ByteBuffer变量放在类之外,以提高性能。您将需要重置ByteBuffer来执行此操作,并且该方法将不再是线程安全的。
  • 摘要数组将包含字节值0-255,如果需要其他范围,则必须对它们进行一些数学运算。
  • 如果您想要不同的“随机”结果,则可以添加某种“种子”。例如,更改为ByteBuffer.allocate(12),并添加一个.putInt(seed)。

1

其他人指出,获得所需行为的一种方法是使用哈希函数,也称为“消息摘要函数”。问题在于,这些算法通常基于MD5之类的算法,该算法在密码学上是安全的(即,真的,真的,真的随机),但是速度非常慢。如果您每次需要随机像素时都使用加密哈希函数,则会遇到非常严重的性能问题。

但是,有一些非加密哈希函数可以产生足够随机的值以达到您的目的,同时又很快。我通常要达到的目标是杂音。我不是Java用户,但似乎至少有一个Java实现可用。如果您发现确实需要从每个像素的坐标中生成每个像素,而不是一次生成所有像素并将它们存储在纹理中,那么这将是一个很好的方法。


1

我将使用超过2000的质数(最大典型分辨率),
这将最小化(或消除重复的种子)

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

Random足够随机。您使用错误的原因有两个。

  • 它的设计目的不是要反复播种。随机属性仅适用于单个随机数序列。
  • Integer.valueOf(Integer.toString(x)+Integer.toString(y))您要播种的像素之间存在巨大的相关性。

我只是使用以下代码的一些变体,您可以在/programming/9624963/java-simplest-integer-的答案中选择哈希函数(不要使用Integer.getHashCode)。杂凑

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

哈希函数可能在哪里


0

您可以尝试将系统当前的时钟时间用作种子,如下所示:

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

希望它产生一个更随机的值。


但是,这不会每次都为该座席坐标创建该座席值。
蜻蜓

0

这是我想到的单行静态着色器功能-poltergeist(嘈杂的幽灵)。

它需要一个2D坐标和一个种子,并根据要求以单调渲染。无论屏幕分辨率如何,它都以实时fps运行。这就是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)); 
}

在任何支持GL(几乎与屏幕兼容的设备)(移动设备)上的分辨率,纹理都可以。

看到它现在在这里运行!

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

您可以使用标准opengl在Java程序中轻松包含此着色器,或使用标准webgl在任何浏览器中轻松包含此着色器。

只是为了好玩,我拒绝任何人在所有设备上在质量和性能上击败Poltergeist的挑战。嘈杂的鬼规则!不败!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.