如何从 X/Y 坐标确定性地生成伪随机模式?

发布于 2024-12-26 07:03:09 字数 514 浏览 8 评论 0原文

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

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

然后,我尝试通过连接坐标来创建种子,如下所示:

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

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

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

I'm writing a shader which occasionally makes a point sparkle on a 2D map. (The "sparkle" is simply a brighter-colored pixel.) I'd like the sparkled blocks to appear randomly and uniformly distributed on the (infinite) plane, but I want the sparkling to be deterministic based on X and Y coordinates. I tried creating seed from the coordinates and creating a Java Random from that seed, but my attempts have so far resulted in recognizable patterns. This function will be called frequently (many millions of times) so performance is critical.

I first tried to mimic my hashCode() implementation, which uses a prime number multiplier to avoid collisions. This resulted in a visible gash across the map where a series of points shared the same seed.

I then tried to create a seed by concatenating the coordinates like so:

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

This seems to result in patterned data as well, though the pattern isn't as obvious. The selected coordinates appear in lines, not evenly distributed at all.

I've avoided using MD5 or other cryptographic hashing algorithms because I'm afraid of the performance impact.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

莫相离 2025-01-02 07:03:09

以下是一个非常有效的函数,用于以伪随机但确定性的方式混合位:

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 一样好,但速度更快......

The following is a very efficient function for mixing bits in a pseudo-random but deterministic fashion:

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

So if you want a pseudo-random long result from x and y co-ordinates you could do something like:

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

I've used this approach successfully in graphics before, gives pretty good results! The quality of random numbers is about as good as java.util.Random but it is much faster....

樱&纷飞 2025-01-02 07:03:09

线性同余生成器java.util.Random 的优点是可重复用于任何选定的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();
    }
}

The linear congruential generator implemented in java.util.Random has the advantage of being repeatable for any chosen SEED. Given these declarations,

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

You can initialize a (repeatable) list of N randomly chosen points in the rectangle (0, 0, MAX_X, MAX_Y) as follows:

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

It may be convenient to give each point a Timer whose period is chosen from the same sequence:

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();
    }
}
小猫一只 2025-01-02 07:03:09

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

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 是一种易于理解的算法,并且加密安全性对于此应用程序来说并不重要。不过,我肯定想要更快(而且不那么混乱)的东西。

This is something I did which works (produces the desired effect) but definitely isn't perfect.

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

Aside from being particularly verbose, the use of the Random is probably excessive since I only pull call nextInt() twice. It's useful for generating values in a specific range, but I should be able to do that with modulo arithmetic anyway.

I like that MD5 is a well-understood algorithm, and cryptographic security is not important for this application. I would definitely like something faster (and less messy), though.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文