GLSL的随机/噪声功能

Kos*_*Kos 167 random shader glsl noise perlin-noise

由于GPU驱动程序供应商通常不愿意noiseX在GLSL中实现,我正在寻找"图形随机化瑞士军刀"效用函数集,最好在GPU着色器中进行优化.我更喜欢GLSL,但任何语言代码都可以为我做,我可以将它自己翻译成GLSL.

具体来说,我希望:

a)伪随机函数 - 在[-1,1]或[0,1]上的N维,均匀分布,从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.
Run Code Online (Sandbox Code Playgroud)

b)像Perlin Noise这样的连续噪声 - 再次,N维,+ - 均匀分布,具有约束的值集合,并且看起来很好(配置外观像Perlin级别的一些选项也可能是有用的).我希望签名如下:

float noise  (T coord, TT seed);
vec2  noise2 (T coord, TT seed);
// ...
Run Code Online (Sandbox Code Playgroud)

我不太关注随机数生成理论,所以我最急切地想要一个预先制定的解决方案,但我也很感激"这是一个非常好,高效的1D兰特()这些答案,让我解释一下你如何在它上面制作一个好的N维兰特()...".

app*_*pas 250

对于非常简单的伪随机内容,我使用我在互联网上找到的oneliner:

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

您还可以使用您喜欢的任何PRNG生成噪声纹理,然后以正常方式上传并在着色器中对值进行采样; 如果你愿意,我可以稍后挖掘一下代码示例.

另外,请参阅Stefan Gustavson的这个文件,了解Perlin和Simplex噪声的GLSL实现.

  • 3个魔数的意义是什么? (17认同)
  • 你如何使用`vec2 co`?是范围?种子? (10认同)
  • 谨防使用此算法的低精度浮点片段着色器(例如,S3的ARM Mali):http://stackoverflow.com/questions/11293628/noise-algorithm-fails-in-samsung-galaxy-siii-gles/15846469 #15846469.https://github.com/ashima/webgl-noise项目似乎没有低压问题. (10认同)
  • FWIW,这里描述的功能[这里]更详细地讨论(http://stackoverflow.com/questions/12964279/whats-the-origin-of-this-glsl-rand-one-liner). (4认同)
  • 仅供参考:该功能的分布非常糟糕. (3认同)
  • 我是GLSL的新手,有人可以解释为什么使用`co.xy`而不是`co`? (3认同)
  • 如果不是很棒的 6 字符编辑规则,我会编辑逗号之前的空格,这样它只会出现在...之后...我确信数百个开发人员已经复制粘贴了此内容,然后在他们的代码中手动更正了它。 .-.- (3认同)
  • 请注意,在 GLSL 中使用三角函数的任何内容都具有 *undefined* 精度。我遇到了随机数生成器的问题,依赖于 `sin()`,在 ATI 和 NVIDIA 上没有生成相同的结果。此处接受的答案可能会显示相同的问题,但我尚未确认。Gustavson 的解决方案似乎没有使用任何未定义精度的函数;因此,在不同平台上期望大致相同的结果时使用它可能应该没问题。 (2认同)
  • @kelin可能是vec3变体的遗留物。我已经在[Shadertoy](https://www.shadertoy.com/)的数十个着色器中看到了这种令人讨厌的“哈希”功能。 (2认同)

小智 71

Gustavson的实现使用了一维纹理

不,它不是,自2005年以来.只是人们坚持下载旧版本.您提供的链接上的版本仅使用8位2D纹理.

Ashima和我自己的Ian McEwan的新版本没有使用纹理,但在具有大量纹理带宽的典型桌面平台上以大约一半的速度运行.在移动平台上,无纹理版本可能更快,因为纹理通常是一个重要的瓶颈.

我们积极维护的源代码库是:

https://github.com/ashima/webgl-noise

这里使用无纹理和纹理使用版本的噪声集合(仅使用2D纹理):

http://www.itn.liu.se/~stegu/simplexnoise/GLSL-noise-vs-noise.zip

如果您有任何具体问题,请随时给我发电子邮件(我的电子邮件地址可以在classicnoise*.glsl消息来源中找到.)

  • 是的,我所指的实现,你在@dp链接到的davidcornette.com上的代码,确实使用了1D纹理:`glBindTexture(GL_TEXTURE_1D,*texID);`等等.不清楚你的意思是什么"链接你提供了",因为你引用了我的答案,但答案没有链接到你的实施.我将更新我的答案以澄清我所指的内容,并反映您提供的新信息.将人们描述为"坚持"下载旧版本是一种失真,并不值得你信任. (4认同)

Spa*_*ial 70

我发现你可以使用一个简单的整数哈希函数并将结果插入到float的尾数中.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 );
}
Run Code Online (Sandbox Code Playgroud)

截图:

static.frag中随机(vec3)的输出

我在图像编辑程序中检查了屏幕截图.有256种颜色,平均值为127,这意味着分布均匀并涵盖预期范围.

  • +1是一个好主意和实现.我会质疑声称因为有256种颜色且平均值为127,所以分布必须是均匀的(严格意义上说).它可能是统一的,但我认为我们还不知道.例如,钟形曲线分布可以具有相同的平均值和颜色数,但不是均匀的. (12认同)
  • 通过我对直方图的感知,它看起来非常均匀......我认为它对于大多数需要均匀性的应用来说已经足够了.(似乎生成的唯一值小于其他值的是0和255) (3认同)
  • 如何实现此方法的“vec3 random(vec3 v)”重载? (2认同)

Dom*_*ano 23

黄金噪音

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

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);
}
Run Code Online (Sandbox Code Playgroud)

立即在浏览器中查看Gold Noise!

在此输入图像描述

截至2017年9月9日,此函数改进了@appas'答案中当前函数的随机分布:

在此输入图像描述

@appas函数也是不完整的,因为没有提供种子(uv不是种子 - 每帧都相同),并且不适用于低精度芯片组.默认情况下,Gold Noise以低精度运行(更快).

  • 我不认为这与其他噪音功能有任何不同.你有什么证明这有特殊的属性.只是因为你使用一堆无理数不会使它变得特别. (6认同)
  • @Dominic:“它具有优于类似功能的分布”:必须证明这一点。tan() 真的是病态的。接近 pi/2 的 tan() 和接近零的 sqrt() 很可能在不同的硬件上产生不同的结果,因为所有 fract(non-linear*big) 都基于不太重要的位。小的或高的输入值也会影响它。此外,位动态也可能因位置而异。 (3认同)
  • "基于黄金比例" - 引用需要.我没有看到任何比率接近1.618 ......但也许我错过了一些东西. (2认同)
  • @DominicCerisano很抱歉,是这样,但是我能够访问主页,在其上找到的任何链接,我的收藏夹以及其他人的链接,但无法访问您的主页。另外,当我什至无法访问您的页面时,也不要给我“它已被明确记录”,不合理这个词实际上只是在您的实际堆栈溢出帖子上出现过一次,而没有用来描述种子的地方。另外,尝试将fragcoords乘以100,会得到可怕的伪影,同样的东西也只有0.001。 (2认同)
  • @DominicCerisano答:只是那一天,B:你在谈论什么是"改进"我甚至都没有尝试过改进你的代码,你可以尝试测试100和0.001,不需要对你自己的代码撒谎你可以自己测试一下......下次怎么样而不是侮辱那些向你展示你的代码会产生不稳定结果的人,而你却试图弄清楚为什么会出现问题? (2认同)
  • 注意:如今 GLSL 具有整数,因此当需要具有相似性能的质量分布(和动态)时,不再有任何理由不使用“严肃的”基于 int 的哈希生成器。(极低端设备除外)。 (2认同)

Kai*_*ack 14

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

从今天的角度来看,该算法所得出的随机数的均匀性非常差。根据输入值的大小,均匀性会受到很大影响,并且在针对光线/路径追踪应用等进行采样时,可见伪影/图案将变得明显。

为此任务设计了许多不同的函数(其中大多数是整数哈希),针对不同的输入和输出维度,其中大多数正在 2020 年 JCGT 论文《GPU 渲染的哈希函数》中进行评估。根据您的需要,您可以从该论文中建议的函数列表中选择一个函数,也可以从随附的 Shadertoy 中选择一个函数。 本文未涵盖但对我非常有用的一个,在任何输入幅度值上没有任何明显的模式,这也是我想强调的一个。

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

另一种可能性是所谓的“同余生成器”,“线性同余生成器”(LCG) 或“置换同余生成器”(PCG),其与基于散列的算法的不同之处在于 LCG 和 PCG 具有“状态”和关联的“状态”。函数改变这个状态,从中提取伪随机数。因此,它们不依赖于任何“输入”来计算哈希值,而只是转移/排列/变异一个状态,然后在函数的多次调用中进行。PCG 的一种应用可以在Nvidia 的“Mini Path-Tracer Tutorial”中找到。

还有一些类别使用​​的算法可以产生具有所需频谱的噪声模式,例如蓝色噪声,这对眼睛来说特别“令人愉悦”。

(我意识到好的StackOverflow 答案应该以源代码的形式提供算法,而不是以链接的形式提供,因为它们可能会损坏,但现在有太多不同的算法,我打算让这个答案成为当今已知的良好算法的摘要)


Lar*_*rsH 11

McEwan和@StefanGustavson 在这里描述一个很好的实现,它看起来像Perlin噪音,但"不需要任何设置,即不需要纹理也不需要统一数组.只需将它添加到着色器源代码并在任何地方调用它".

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


Fab*_*RET 8

hash:现在有了 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));
}
Run Code Online (Sandbox Code Playgroud)


小智 5

使用这个:

highp float rand(vec2 co)
{
    highp float a = 12.9898;
    highp float b = 78.233;
    highp float c = 43758.5453;
    highp float dt= dot(co.xy ,vec2(a,b));
    highp float sn= mod(dt,3.14);
    return fract(sin(sn) * c);
}
Run Code Online (Sandbox Code Playgroud)

不要使用这个:

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

您可以在OpenGL ES 2.0 的规范单行 GLSL rand() 的改进中找到解释