由于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()……”.


当前回答

我也有同样的问题,我需要在WebGL 1.0中实现它,所以我不能使用之前回答中给出的一些例子。我尝试了前面提到的Gold Noise,但是PHI的使用并不适合我。(距离(xy * PHI, xy) *种子就等于长度(xy) * (1.0 - PHI) *种子,所以我不明白当它直接乘以种子时,PHI的魔力应该如何发挥作用?

不管怎样,我做了类似的事情,只是没有PHI,而是在另一个地方添加了一些变化,基本上我取xy与框架外的某个随机点之间的距离的tan,然后乘以xy与另一个这样的随机点之间的距离,位于左下角(所以这些点之间没有偶然的匹配)。在我看来还不错。单击生成新的帧。

(function main() { const dim = [512, 512]; twgl.setDefaults({ attribPrefix: "a_" }); const gl = twgl.getContext(document.querySelector("canvas")); gl.canvas.width = dim[0]; gl.canvas.height = dim[1]; const bfi = twgl.primitives.createXYQuadBufferInfo(gl); const pgi = twgl.createProgramInfo(gl, ["vs", "fs"]); gl.canvas.onclick = (() => { twgl.bindFramebufferInfo(gl, null); gl.useProgram(pgi.program); twgl.setUniforms(pgi, { u_resolution: dim, u_seed: Array(4).fill().map(Math.random) }); twgl.setBuffersAndAttributes(gl, pgi, bfi); twgl.drawBufferInfo(gl, bfi); }); })(); <script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script> <script id="vs" type="x-shader/x-vertex"> attribute vec4 a_position; attribute vec2 a_texcoord; void main() { gl_Position = a_position; } </script> <script id="fs" type="x-shader/x-fragment"> precision highp float; uniform vec2 u_resolution; uniform vec2 u_seed[2]; void main() { float uni = fract( tan(distance( gl_FragCoord.xy, u_resolution * (u_seed[0] + 1.0) )) * distance( gl_FragCoord.xy, u_resolution * (u_seed[1] - 2.0) ) ); gl_FragColor = vec4(uni, uni, uni, 1.0); } </script> <canvas></canvas>

其他回答

这里还有一个由McEwan和@StefanGustavson描述的很好的实现,看起来像柏林噪声,但“不需要任何设置,即不需要纹理或统一数组。只需将它添加到你的着色器源代码,并在任何你想要的地方调用它”。

这非常方便,特别是考虑到Gustavson早期的实现,@dep链接到,使用1D纹理,这在GLSL ES (WebGL的着色语言)中是不支持的。

我也有同样的问题,我需要在WebGL 1.0中实现它,所以我不能使用之前回答中给出的一些例子。我尝试了前面提到的Gold Noise,但是PHI的使用并不适合我。(距离(xy * PHI, xy) *种子就等于长度(xy) * (1.0 - PHI) *种子,所以我不明白当它直接乘以种子时,PHI的魔力应该如何发挥作用?

不管怎样,我做了类似的事情,只是没有PHI,而是在另一个地方添加了一些变化,基本上我取xy与框架外的某个随机点之间的距离的tan,然后乘以xy与另一个这样的随机点之间的距离,位于左下角(所以这些点之间没有偶然的匹配)。在我看来还不错。单击生成新的帧。

(function main() { const dim = [512, 512]; twgl.setDefaults({ attribPrefix: "a_" }); const gl = twgl.getContext(document.querySelector("canvas")); gl.canvas.width = dim[0]; gl.canvas.height = dim[1]; const bfi = twgl.primitives.createXYQuadBufferInfo(gl); const pgi = twgl.createProgramInfo(gl, ["vs", "fs"]); gl.canvas.onclick = (() => { twgl.bindFramebufferInfo(gl, null); gl.useProgram(pgi.program); twgl.setUniforms(pgi, { u_resolution: dim, u_seed: Array(4).fill().map(Math.random) }); twgl.setBuffersAndAttributes(gl, pgi, bfi); twgl.drawBufferInfo(gl, bfi); }); })(); <script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script> <script id="vs" type="x-shader/x-vertex"> attribute vec4 a_position; attribute vec2 a_texcoord; void main() { gl_Position = a_position; } </script> <script id="fs" type="x-shader/x-fragment"> precision highp float; uniform vec2 u_resolution; uniform vec2 u_seed[2]; void main() { float uni = fract( tan(distance( gl_FragCoord.xy, u_resolution * (u_seed[0] + 1.0) )) * distance( gl_FragCoord.xy, u_resolution * (u_seed[1] - 2.0) ) ); gl_FragColor = vec4(uni, uni, uni, 1.0); } </script> <canvas></canvas>

我已经将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中提供的一些硬件加速函数来优化翻译后的代码。如果我最终这样做了,我会更新这篇文章。

黄金的噪音

// Gold Noise ©2015 dcerisano@standard3d.com
// - based on the Golden Ratio
// - uniform normalized distribution
// - fastest static noise generator function (also runs at low precision)
// - use with indicated fractional seeding method. 

float PHI = 1.61803398874989484820459;  // Φ = Golden Ratio   

float gold_noise(in vec2 xy, in float seed){
       return fract(tan(distance(xy*PHI, xy)*seed)*xy.x);
}

现在就在浏览器中查看黄金噪音!

这个函数在2017年9月9日@appas的回答中改善了当前函数的随机分布:

@appas函数也是不完整的,因为没有提供种子(uv不是种子-每帧都是一样的),并且不能与低精度芯片组一起工作。默认情况下,Gold Noise以低精度运行(快得多)。

我突然想到,你可以使用一个简单的整数哈希函数,并将结果插入到浮点数的尾数中。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,这意味着分布均匀,涵盖了预期的范围。