这个 GLSL rand()一行程序的起源是什么?

我见过这个用于着色器的伪随机数生成器,它被称为 网上到处都是:

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

它有各种各样的名称,叫做“规范”,或者“我在网上找到的一句俏皮话”。

这个函数的起源是什么?常数值是否像它们看起来那样武断,或者它们的选择是否有一些艺术性?是否对这一职能的优点进行了讨论?

编辑: 这个函数最古老的参考是 this archive from Feb '08,原来的页面现在已经从网络上消失了。但那里没有比其他地方更多的讨论了。

40459 次浏览

Very interesting question!

我试图在输入答案的同时弄清楚这个问题:) 首先是一个简单的方法来玩它: < a href = “ http://www.wolframalpha.com/input/? i = plot% 28.20mod% 28.20sin% 28x% 2a12.9898.20% 2b% 20y% 2a78.233% 29% 20% 2a% 2043758.5453,1% 29x = 0.2,% 20y = 0.2% 29”> 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 都是一样的,这是一个散列函数!

The first thing the function does is to go from 2d to 1d. That is not interesting in itself, but the numbers are chosen so they do not repeat typically. Also we have a floating point addition there. There will be a few more bits from y or x, but the numbers might just be chosen right so it does a mix.

然后我们示例一个黑盒 sin ()函数,这在很大程度上取决于实现!

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

我不认为这是一个好的散列函数在一般情况下。Sin ()是 GPU 上的一个黑盒,从数字上看。通过使用几乎任何散列函数并转换它,应该可以构造一个更好的散列函数。困难的部分是将 CPU 哈希中使用的典型整数操作转换为浮点(一半或32位)或固定点操作,但这应该是可能的。

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

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

高振幅窦的模数大于1乘以4000是一个周期函数。它就像一个窗帘或者一个波纹金属制成的非常小,因为它被乘以4000,然后通过点积以一个角度旋转。

由于这个函数是2-d 的,点积的作用就是使周期函数相对于 x 轴和 y 轴成一个倾斜的方向。大约是13/79的比例。这是低效的,你实际上可以达到同样的做窦(13x + 79y)这也将达到同样的事情,我认为用较少的数学。.

如果你在 X 和 Y 中找到函数的周期,你可以取样,这样它看起来又像一个简单的正弦波。

这是一张在 图表中放大的图片

我不知道它的起源,但是它和其他很多东西很相似,如果你在图形中定期使用它,它会产生莫尔条纹,你可以看到它最终会再次出现。

也许它是一些非递归的混沌映射,那么它可以解释很多东西,也可以只是一些大数的任意操纵。

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

来源可能是纸张: “在 y = [(a + x) sin (bx)] mod 1的帮助下生成随机数”,W.J.J. 雷伊,第22届欧洲统计学家会议和第7届维尔纽斯会议关于概率论和数学统计,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。

注意,这是一个流。转换为1D hash’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
}

我不相信这是真正的起源,但 OP 的代码作为代码示例出现在 Patricio Gonzalez Vivo 和 Jen Lowe (https://thebookofshaders.com/10/)的“ The Book of Shaders”中。在他们的代码中,Patricio Gonzales Vivo 被引用为作者,即“//Author@patriciogv-2015”

由于 OP 的研究可以追溯到更久远的年代(2008年) ,资料来源至少可以解释它的受欢迎程度,作者也许可以对他的资料来源提供一些解释。