我正在编写一个着色器,它偶尔会在2D贴图上产生点闪光。("闪光"只是一个颜色更亮的像素。)我希望闪光块在(无限)平面上随机均匀分布,但我希望闪光基于X和Y坐标具有确定性。我尝试从坐标创建种子,并从该种子创建Java Random
,但到目前为止,我的尝试已经产生了可识别的模式。此函数将被频繁调用(数百万次),因此性能至关重要。
我首先尝试模拟我的hashCode()
实现,它使用素数乘法器来避免冲突。这导致地图上出现了一道可见的裂缝,一系列点共享同一种子。
然后,我试图通过连接坐标来创建一个种子,如下所示:
long seed = ((long) x << 32) | (long) y;
Random rand = new Random(seed);
这似乎也产生了模式化的数据,尽管模式并没有那么明显。选定的坐标显示为直线,而不是均匀分布。
我避免使用MD5或其他加密哈希算法,因为我担心性能会受到影响。
以下是一个非常有效的函数,用于以伪随机但具有确定性的方式混合比特:
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一样好,但速度要快得多。。。。
在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();
}
}
这是我做的事情,它有效(产生了所需的效果),但肯定不是完美的。
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是一种很好理解的算法,加密安全性对这个应用程序来说并不重要。不过,我肯定想要更快(而且不那么乱)的。