【问题标题】:How can I produce a pseudorandom pattern from X/Y coordinates deterministically?如何确定地从 X/Y 坐标生成伪随机模式?
【发布时间】:2012-01-13 02:55:26
【问题描述】:

我正在编写一个着色器,它偶尔会使一个点在 2D 地图上闪闪发光。 (“闪光”只是一个颜色更亮的像素。)我希望闪光块随机均匀地分布在(无限)平面上,但我希望闪光是基于 X 和 Y 坐标的确定性。我尝试从坐标创建种子并从该种子创建 Java Random,但到目前为止,我的尝试已经产生了可识别的模式。此函数将被频繁调用(数百万次),因此性能至关重要。

我首先尝试模仿我的hashCode() 实现,它使用质数乘数来避免冲突。这会导致地图上出现明显的裂痕,其中一系列点共享同一个种子。

然后我尝试通过像这样连接坐标来创建种子:

long seed = ((long) x << 32) | (long) y;
Random rand = new Random(seed);

这似乎也会产生模式化的数据,尽管模式并不那么明显。选定的坐标出现在直线上,根本不均匀分布。

我已经避免使用 MD5 或其他加密哈希算法,因为我担心会影响性能。

【问题讨论】:

  • 如果您从 lcm 生成数百万个伪随机数并在二维正方形中绘制,那么您很可能会看到可识别的“模式”,除非您使用加密强生成器。搜索 k 平面。您可能想使用非线性同余伪随机数生成器。

标签: java hash coordinates seed deterministic


【解决方案1】:

以下是以伪随机但确定的方式混合位的非常有效的函数:

public static final long xorShift64(long a) {
    a ^= (a << 21);
    a ^= (a >>> 35);
    a ^= (a << 4);
    return a;
}

因此,如果您想要 x 和 y 坐标的伪随机长结果,您可以执行以下操作:

    long mix = xorShift64(x) + Long.rotateLeft(xorShift64(y),32) + 0xCAFEBABE;
    long result = xorShift64(mix);

我以前在图形中成功使用过这种方法,效果非常好!随机数的质量与 java.util.Random 差不多,但速度要快得多....

【讨论】:

  • 看起来很有趣。你知道任何评估这种结构的抗碰撞性的文章吗? (出于小地图的目的,这不太重要,但我很好奇。)
【解决方案2】:

java.util.Random 中实现的linear congruential generator 的优点是对于任何选定的SEED 都是可重复的。鉴于这些声明,

private static final int SEED = 42;
private static final int N = 128;
private static final int MAX_X = 1024;
private static final int MAX_Y = 1024;
private final Random rnd = new Random(SEED);
private final List<SparklePoint> list = new ArrayList<SparklePoint>(N);

您可以在矩形(0, 0, MAX_X, MAX_Y) 中初始化N 随机选择的点的(可重复)列表,如下所示:

public void init(int seed) {
    for (int i = 0; i < N; i++) {
        int x = rnd.nextInt(MAX_X);
        int y = rnd.nextInt(MAX_Y);
        list.add(new SparklePoint(x, y));
    }
}

给每个点一个Timer 可能会很方便,它的周期是从相同的序列中选择的:

private class SparklePoint implements ActionListener {

    private static final int MAX_DELAY = 1000;
    private final Point p;
    private final Timer t;
    private boolean bright;

    public SparklePoint(int x, int y) {
        p = new Point(x, y);
        t = new Timer(rnd.nextInt(MAX_DELAY), this);
        t.setRepeats(false);
        t.start();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        t.stop();
        if (bright) {
            // darken p
        } else {
            // brighten p
        }
        bright = !bright;
        t.setDelay(rnd.nextInt(MAX_DELAY));
        t.start();
    }
}

【讨论】:

  • 挑战是从一开始就生成种子。我不能使用常量种子,因为在任何给定时间,我只绘制地图的一小部分。如果我在远离原点的地方绘图,我不想让 Random 旋转直到它到达我正在绘制的坐标。
  • 啊,我以为选择的点集保持不变;我可以看到优化视图以忽略当前可见区域之外的点。
【解决方案3】:

这是我所做的,它有效(产生了预期的效果)但绝对不是完美的。

MessageDigest md5;
try {
    md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
    return null;
}
md5.update(new byte[] {
    (byte)(x >>> 24),
    (byte)(x >>> 16),
    (byte)(x >>> 8),
    (byte)x,
    (byte)(z >>> 24),
    (byte)(z >>> 16),
    (byte)(z >>> 8),
    (byte)z
}, 0, 8);
byte[] digest = md5.digest();
long seed = digest[0] + (digest[1] << 8) + (digest[2] << 16) + (digest[3] << 24) + (digest[4] << 32) + (digest[5] << 40) + (digest[6] << 48) + (digest[7] << 56);
Random random = new Random(seed);

除了特别冗长之外,Random 的使用可能是过度的,因为我只调用了两次nextInt()。它对于生成特定范围内的值很有用,但无论如何我应该能够使用模算术来做到这一点。

我喜欢 MD5 是一种易于理解的算法,加密安全性对于这个应用程序并不重要。不过,我肯定会想要更快(更简洁)的东西。

【讨论】:

    猜你喜欢
    • 2013-11-09
    • 1970-01-01
    • 1970-01-01
    • 2017-06-14
    • 2018-05-20
    • 1970-01-01
    • 2017-08-30
    • 2021-10-03
    • 2019-12-05
    相关资源
    最近更新 更多