108

我已经看到这个伪随机数生成器在网络上到处引用的着色器中使用:

float rand(vec2 co){
  return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

它被称为“规范”或“我在网上某处找到的单行”。

这个函数的起源是什么?常数值是否像它们看起来那样随意,或者它们的选择有什么艺术?有没有讨论这个功能的优点?

编辑:我遇到的对这个函数的最古老的引用是这个 08 年 2 月的档案,原来的页面现在已经从网上消失了。但是那里没有比其他任何地方更多的讨论。

4

5 回答 5

43

非常有趣的问题!

我试图在输入答案时弄清楚这一点:) 首先是一个简单的方法:http ://www.wolframalpha.com/input/?i=plot%28+mod%28+sin%28x*12.9898 +%2B+y*78.233%29++*+43758.5453%2C1%29x%3D0..2%2C+y%3D0..2%29

然后让我们考虑一下我们在这里要做什么:对于两个输入坐标 x,y,我们返回一个“随机数”。现在这不是一个随机数。每次我们输入相同的 x,y 时都是一样的。这是一个哈希函数!

该函数所做的第一件事是从 2d 到 1d。这本身并不有趣,但是选择了数字,因此它们通常不会重复。我们还有一个浮点加法。y 或 x 将有更多位,但可能只是选择了正确的数字,因此它可以混合。

然后我们采样一个黑盒 sin() 函数。这在很大程度上取决于实施!

最后,它通过乘以分数来放大 sin() 实现中的错误。

在一般情况下,我认为这不是一个好的哈希函数。sin() 在数字上是 GPU 上的一个黑盒子。通过采用几乎任何散列函数并对其进行转换,应该可以构建一个更好的函数。困难的部分是将 cpu 散列中使用的典型整数运算转换为浮点(半位或 32 位)或定点运算,但应该是可能的。

同样,这个作为散列函数的真正问题是 sin() 是一个黑盒子。

于 2012-10-30T03:57:55.823 回答
26

起源可能是论文:“On generate random numbers, with help of y= [(a+x)sin(bx)] mod 1”, WJJ Rey, 22nd European Meeting of Statisticians and the 7th Vilnius Conference on Probability Theory and数理统计,1998 年 8 月

编辑:由于我找不到本文的副本并且“TestU01”参考可能不清楚,这是伪 C 中 TestU01 中描述的方案:

#define A1 ???
#define A2 ???
#define B1 pi*(sqrt(5.0)-1)/2
#define B2 ???

uint32_t n;   // position in the stream

double next() {
  double t = fract(A1     * sin(B1*n));
  double u = fract((A2+t) * sin(B2*t));
  n++;
  return u;
} 

其中唯一推荐的常数值是 B1。

请注意,这是针对流的。转换为一维散列“n”成为整数网格。所以我的猜测是有人看到了这个并将't'转换为一个简单的函数f(x,y)。使用上面的原始常量将产生:

float hash(vec2 co){
  float t = 12.9898*co.x + 78.233*co.y; 
  return fract((A2+t) * sin(t));  // any B2 is folded into 't' computation
}
于 2015-12-11T12:37:50.710 回答
8

常数值是任意的,特别是它们非常大,并且距离素数有几个小数。

hi 幅度正弦的 1 上的模数乘以 4000 是一个周期函数。它就像百叶窗或波纹金属制成的非常小,因为它乘以 4000,并通过点积旋转一个角度。

由于函数是二维的,点积具有将周期函数相对于 X 和 Y 轴倾斜转动的效果。大约按 13/79 的比例计算。这是低效的,你实际上可以通过做(13x + 79y)的正弦来达到同样的效果,这也可以用更少的数学来实现我认为的同样的事情..

如果在 X 和 Y 中都找到函数的周期,则可以对其进行采样,使其再次看起来像一个简单的正弦波。

这是放大图的图片

我不知道起源,但它与许多其他类似,如果你在图形中定期使用它,它往往会产生莫尔图案,你可以看到它最终会再次出现。

于 2013-09-21T07:39:28.047 回答
2

我不认为这是真正的起源,但 OP 的代码在 Patricio Gonzalez Vivo 和 Jen Lowe 的“着色器之书”中作为代码示例呈现 ( https://thebookofshaders.com/10/ )。在他们的代码中,Patricio Gonzales Vivo 被引用为作者,即“// Author @patriciogv - 2015”

由于 OP 的研究可以追溯到更早(到 08 年),因此来源至少可以解释它的受欢迎程度,并且作者可能能够对他的来源有所了解。

于 2021-05-23T19:57:53.003 回答
1

也许它是一些非循环的混沌映射,那么它可以解释很多事情,但也可以只是一些大数字的任意操作。

编辑:基本上,函数 fract(sin(x) * 43758.5453) 是一个简单的类似散列的函数, sin(x) 提供 -1 到 1 之间的平滑 sin 插值,因此 sin(x) * 43758.5453 将从 - 43758.5453 至 43758.5453。这是一个相当大的范围,因此即使 x 中的小步长也会在结果中提供大步长,并且小数部分的变化非常大。需要“分数”来获取 -0.99... 到 0.999... 范围内的值。现在,当我们有类似哈希函数的东西时,我们应该从向量创建用于生产哈希的函数。最简单的方法是为 x 输入向量的任何 y 分量单独调用“散列”。但是,我们将有一些对称值。因此,我们应该从向量中获取一些值,方法是找到一些随机向量并找到该向量的“点”积,我们开始:fract(sin(dot(co.xy , vec2(12.9898,78.233))) * 43758.5453); 此外,根据选择的向量,它的长度应该足够长,以便在计算“点”积之后有几个“sin”函数的周期。

于 2014-02-06T16:11:33.753 回答