由于GPU驱动供应商通常不会费心在GLSL中实现noiseX,我正在寻找一个“图形随机化瑞士军刀”实用函数集,最好是在GPU着色器中优化使用。我更喜欢GLSL,但是任何语言的代码对我来说都可以,我可以把它翻译成GLSL。

具体来说,我希望:

a)伪随机函数- n维,均匀分布于[-1,1]或[0,1]之上,从m维种子(理想情况下是任何值,但我同意将种子限制在,比如说,0..1为结果均匀分布)。喜欢的东西:

float random  (T seed);
vec2  random2 (T seed);
vec3  random3 (T seed);
vec4  random4 (T seed);
// T being either float, vec2, vec3, vec4 - ideally.

b)连续的噪声,比如柏林噪声——同样是n维,+-均匀分布,有约束的值集,看起来不错(一些配置外观的选项,比如柏林电平也很有用)。我希望签名是这样的:

float noise  (T coord, TT seed);
vec2  noise2 (T coord, TT seed);
// ...

我对随机数生成理论不太感兴趣,所以我最渴望的是一个现成的解决方案,但我也很喜欢这样的回答:“这里有一个非常好的,高效的1D rand(),让我解释一下如何在它的基础上制作一个好的n维rand()……”.


当前回答

在2010年首次发布这个问题之后,在良好的随机函数和硬件支持方面发生了很大的变化。

从今天的角度来看,这个算法抽取的随机数的一致性很差。均匀性受到输入值大小的很大影响,当从输入值中采样时,例如射线/路径跟踪应用程序,可见的工件/模式将变得明显。

There have been many different functions (most of them integer hashing) being devised for this task, for different input and output dimensionality, most of which are being evaluated in the 2020 JCGT paper Hash Functions for GPU Rendering. Depending on your needs you could select a function from the list of proposed functions in that paper and simply from the accompanying Shadertoy. One that isn't covered in this paper but that has served me very well without any noticeably patterns on any input magnitude values is also one that I want to highlight.

其他类型的算法使用低差异序列来绘制伪随机数,例如带有Owen-Nayar置乱的Sobol序列。Eric Heitz在这一领域做了一些惊人的研究,以及他在Screen Space论文中将蒙特卡罗误差分布为蓝色噪声的低差异采样器。 另一个例子是(迄今为止最新的)JCGT论文基于实用哈希的Owen置乱,它将Owen置乱应用于不同的哈希函数(即Laine-Karras)。

然而,其他类别使用算法来产生具有理想频谱的噪声模式,例如蓝色噪声,它特别“取悦”眼睛。

(我意识到好的StackOverflow答案应该提供算法作为源代码,而不是链接,因为那些可能会中断,但现在有太多不同的算法,我打算把这个答案作为今天已知的好算法的总结)

其他回答

我突然想到,你可以使用一个简单的整数哈希函数,并将结果插入到浮点数的尾数中。IIRC, GLSL规范保证32位无符号整数和IEEE binary32浮点表示,因此它应该是完全可移植的。

我刚才试过了。结果非常好:我尝试的每个输入看起来都像静态的,根本没有可见的模式。相比之下,在相同的输入条件下,流行的sin/fract代码片段在我的GPU上有相当明显的对角线。

一个缺点是它需要GLSL v3.30。虽然它看起来足够快,但我还没有对它的表现进行实证量化。AMD的Shader Analyzer声称HD5870上的vec2版本每个时钟有13.33个像素。与sin/fract片段的每个时钟16像素相比。所以它肯定会慢一点。

这是我的实现。我把它写成了不同的形式这样更容易推导出你自己的函数。

/*
    static.frag
    by Spatial
    05 July 2013
*/

#version 330 core

uniform float time;
out vec4 fragment;



// A single iteration of Bob Jenkins' One-At-A-Time hashing algorithm.
uint hash( uint x ) {
    x += ( x << 10u );
    x ^= ( x >>  6u );
    x += ( x <<  3u );
    x ^= ( x >> 11u );
    x += ( x << 15u );
    return x;
}



// Compound versions of the hashing algorithm I whipped together.
uint hash( uvec2 v ) { return hash( v.x ^ hash(v.y)                         ); }
uint hash( uvec3 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z)             ); }
uint hash( uvec4 v ) { return hash( v.x ^ hash(v.y) ^ hash(v.z) ^ hash(v.w) ); }



// Construct a float with half-open range [0:1] using low 23 bits.
// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0.
float floatConstruct( uint m ) {
    const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask
    const uint ieeeOne      = 0x3F800000u; // 1.0 in IEEE binary32

    m &= ieeeMantissa;                     // Keep only mantissa bits (fractional part)
    m |= ieeeOne;                          // Add fractional part to 1.0

    float  f = uintBitsToFloat( m );       // Range [1:2]
    return f - 1.0;                        // Range [0:1]
}



// Pseudo-random value in half-open range [0:1].
float random( float x ) { return floatConstruct(hash(floatBitsToUint(x))); }
float random( vec2  v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec3  v ) { return floatConstruct(hash(floatBitsToUint(v))); }
float random( vec4  v ) { return floatConstruct(hash(floatBitsToUint(v))); }





void main()
{
    vec3  inputs = vec3( gl_FragCoord.xy, time ); // Spatial and temporal inputs
    float rand   = random( inputs );              // Random per-pixel value
    vec3  luma   = vec3( rand );                  // Expand to RGB

    fragment = vec4( luma, 1.0 );
}

截图:

我在一个图像编辑程序中检查了截图。有256种颜色,平均值是127,这意味着分布均匀,涵盖了预期的范围。

刚找到这个版本的GPU 3d噪声,据说它是最快的一个可用的:

#ifndef __noise_hlsl_
#define __noise_hlsl_

// hash based 3d value noise
// function taken from https://www.shadertoy.com/view/XslGRr
// Created by inigo quilez - iq/2013
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

// ported from GLSL to HLSL

float hash( float n )
{
    return frac(sin(n)*43758.5453);
}

float noise( float3 x )
{
    // The noise function returns a value in the range -1.0f -> 1.0f

    float3 p = floor(x);
    float3 f = frac(x);

    f       = f*f*(3.0-2.0*f);
    float n = p.x + p.y*57.0 + 113.0*p.z;

    return lerp(lerp(lerp( hash(n+0.0), hash(n+1.0),f.x),
                   lerp( hash(n+57.0), hash(n+58.0),f.x),f.y),
               lerp(lerp( hash(n+113.0), hash(n+114.0),f.x),
                   lerp( hash(n+170.0), hash(n+171.0),f.x),f.y),f.z);
}

#endif

我已经将Ken Perlin的一个Java实现翻译成GLSL,并在ShaderToy的几个项目中使用了它。

以下是我做的GLSL解读:

int b(int N, int B) { return N>>B & 1; }
int T[] = int[](0x15,0x38,0x32,0x2c,0x0d,0x13,0x07,0x2a);
int A[] = int[](0,0,0);

int b(int i, int j, int k, int B) { return T[b(i,B)<<2 | b(j,B)<<1 | b(k,B)]; }

int shuffle(int i, int j, int k) {
    return b(i,j,k,0) + b(j,k,i,1) + b(k,i,j,2) + b(i,j,k,3) +
        b(j,k,i,4) + b(k,i,j,5) + b(i,j,k,6) + b(j,k,i,7) ;
}

float K(int a, vec3 uvw, vec3 ijk)
{
    float s = float(A[0]+A[1]+A[2])/6.0;
    float x = uvw.x - float(A[0]) + s,
        y = uvw.y - float(A[1]) + s,
        z = uvw.z - float(A[2]) + s,
        t = 0.6 - x * x - y * y - z * z;
    int h = shuffle(int(ijk.x) + A[0], int(ijk.y) + A[1], int(ijk.z) + A[2]);
    A[a]++;
    if (t < 0.0)
        return 0.0;
    int b5 = h>>5 & 1, b4 = h>>4 & 1, b3 = h>>3 & 1, b2= h>>2 & 1, b = h & 3;
    float p = b==1?x:b==2?y:z, q = b==1?y:b==2?z:x, r = b==1?z:b==2?x:y;
    p = (b5==b3 ? -p : p); q = (b5==b4 ? -q : q); r = (b5!=(b4^b3) ? -r : r);
    t *= t;
    return 8.0 * t * t * (p + (b==0 ? q+r : b2==0 ? q : r));
}

float noise(float x, float y, float z)
{
    float s = (x + y + z) / 3.0;  
    vec3 ijk = vec3(int(floor(x+s)), int(floor(y+s)), int(floor(z+s)));
    s = float(ijk.x + ijk.y + ijk.z) / 6.0;
    vec3 uvw = vec3(x - float(ijk.x) + s, y - float(ijk.y) + s, z - float(ijk.z) + s);
    A[0] = A[1] = A[2] = 0;
    int hi = uvw.x >= uvw.z ? uvw.x >= uvw.y ? 0 : 1 : uvw.y >= uvw.z ? 1 : 2;
    int lo = uvw.x <  uvw.z ? uvw.x <  uvw.y ? 0 : 1 : uvw.y <  uvw.z ? 1 : 2;
    return K(hi, uvw, ijk) + K(3 - hi - lo, uvw, ijk) + K(lo, uvw, ijk) + K(0, uvw, ijk);
}

我从Ken Perlin's Noise Hardware的第二章附录B中翻译过来:

https://www.csee.umbc.edu/~olano/s2002c36/ch02.pdf

下面是我在Shader Toy上做的一个公共阴影,它使用了张贴噪音功能:

https://www.shadertoy.com/view/3slXzM

在我的研究中,我发现了一些关于噪音的其他好的来源,包括:

https://thebookofshaders.com/11/

https://mzucker.github.io/html/perlin-noise-math-faq.html

https://rmarcus.info/blog/2018/03/04/perlin-noise.html

http://flafla2.github.io/2014/08/09/perlinnoise.html

https://mrl.nyu.edu/~perlin/noise/

https://rmarcus.info/blog/assets/perlin/perlin_paper.pdf

https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch05.html

我强烈推荐这本着色器的书,因为它不仅提供了一个很好的噪音交互解释,而且还提供了其他着色器概念。

编辑:

也许可以通过使用GLSL中提供的一些硬件加速函数来优化翻译后的代码。如果我最终这样做了,我会更新这篇文章。

散列: 现在有webGL2.0,所以整数可以在(w)GLSL中使用。 ->用于高质量的便携式哈希(成本与丑陋的浮动哈希相似),我们现在可以使用“严肃的”哈希技术。 IQ在https://www.shadertoy.com/view/XlXcW4中实现了一些(以及更多)

例如:

  const uint k = 1103515245U;  // GLIB C
//const uint k = 134775813U;   // Delphi and Turbo Pascal
//const uint k = 20170906U;    // Today's date (use three days ago's dateif you want a prime)
//const uint k = 1664525U;     // Numerical Recipes

vec3 hash( uvec3 x )
{
    x = ((x>>8U)^x.yzx)*k;
    x = ((x>>8U)^x.yzx)*k;
    x = ((x>>8U)^x.yzx)*k;

    return vec3(x)*(1.0/float(0xffffffffU));
}

对于非常简单的伪随机的东西,我使用这个在线程序,我在互联网上找到的地方:

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

你也可以使用任何你喜欢的PRNG生成一个噪音纹理,然后以正常的方式上传这个,并在你的着色器中采样值;如果你愿意,我可以稍后再找一个代码样本。

另外,查看Stefan Gustavson的Perlin和Simplex噪声的GLSL实现文件。