208

由于 GPU 驱动程序供应商通常不费心noiseX在 GLSL 中实现,我正在寻找“图形随机化瑞士军刀”实用功能集,最好优化以在 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)像 Perlin 噪声这样的连续噪声- 再次,N 维,+- 均匀分布,具有受限的一组值,而且看起来不错(配置外观的一些选项,如 Perlin 级别也可能很有用)。我希望签名如下:

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

我不太喜欢随机数生成理论,所以我最渴望找到一个预制的解决方案,但我也很欣赏像“这是一个非常好的、高效的 1D rand(),让我解释一下”这样的答案你如何在它上面制作一个好的 N 维 rand() ......”

4

14 回答 14

298

对于非常简单的伪随机的东西,我使用我在互联网某处找到的这个 oneliner:

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

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

此外,请查看此文件以了解 Stefan Gustavson 的 Perlin 和 Simplex 噪声的 GLSL 实现。

于 2010-11-25T09:12:38.677 回答
104

我突然想到你可以使用一个简单的整数散列函数并将结果插入到浮点数的尾数中。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 );
}

截屏:

static.frag 中 random(vec3) 的输出

我在图像编辑程序中检查了屏幕截图。共有 256 种颜色,平均值为 127,表示分布均匀,覆盖了预期范围。

于 2013-07-04T23:40:13.150 回答
77

Gustavson 的实现使用 1D 纹理

不,不是,自 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来源中找到。)

于 2012-03-22T13:55:57.790 回答
42

金噪声

// 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 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);
}

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

在此处输入图像描述

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

在此处输入图像描述

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

于 2015-01-22T17:36:42.237 回答
13

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

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

于 2011-06-07T22:33:49.683 回答
6

使用这个:

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);
}

不要使用这个:

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

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

于 2019-08-07T08:56:32.913 回答
5

在 2010 年首次发布此问题后,良好的随机功能和对它们的硬件支持领域发生了很大变化。

从今天的角度来看已接受的答案,该算法在从中提取的随机数的一致性方面非常糟糕。并且均匀性受到很大影响,具体取决于输入值的大小,并且当从其采样以用于例如光线/路径跟踪应用时,可见伪影/图案将变得明显。

为此任务设计了许多不同的函数(其中大部分是整数散列),用于不同的输入和输出维度,其中大部分正在 2020 JCGT 论文Hash Functions for GPU Rendering中进行评估。根据您的需要,您可以从该论文中建议的函数列表中选择一个函数,也可以从随附的 Shadertoy中选择一个函数。 本文未涉及但对我很有帮助的一个,在任何输入幅度值上没有任何明显的模式,这也是我想强调的一个。

其他类型的算法使用低差异序列从中提取伪随机数,例如带有 Owen-Nayar 加扰的 Sobol 序列。Eric Heitz 在这方面做了一些惊人的研究,以及他的 A Low-Discrepancy Sampler that Distributions Monte Carlo Errors as a Blue Noise in Screen Space论文。另一个例子是(迄今为止最新的)JCGT 论文Practical Hash-based Owen Scrambling,它将 Owen 加扰应用于不同的哈希函数(即 Laine-Karras)。

然而,其他类别使用的算法会产生具有所需频谱的噪声模式,例如蓝噪声,这对眼睛来说特别“令人愉悦”。

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

于 2021-01-04T13:50:41.903 回答
5

哈希:现在有 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));
}
于 2018-09-06T15:28:03.960 回答
2

1d Perlin 的直线锯齿状版本,本质上是随机的 lfo 之字形。

half  rn(float xx){         
    half x0=floor(xx);
    half x1=x0+1;
    half v0 = frac(sin (x0*.014686)*31718.927+x0);
    half v1 = frac(sin (x1*.014686)*31718.927+x1);          

    return (v0*(1-frac(xx))+v1*(frac(xx)))*2-1*sin(xx);
}

我还在 shadertoy 所有者 inigo quilez perlin 教程网站和 voronoi 等网站上发现了 1-2-3-4d perlin noise,他有完整的快速实现和代码。

于 2013-09-25T11:40:58.603 回答
2

我已将 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 的 Noise Hardware 第 2 章的附录 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 中提供的一些硬件加速功能来优化翻译后的代码。如果我最终这样做,将更新这篇文章。

于 2019-02-25T21:20:55.443 回答
1

刚刚发现这个版本的 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
于 2014-02-06T13:49:31.070 回答
1

lygia,一个多语言着色器库

如果您不想将函数复制/粘贴到着色器中,也可以使用多语言着色器库lygia 。它包含一些生成函数,如 GLSL 和 HLSL 中的 cnoise、fbm、noised、pnoise、random、snoise。还有许多其他很棒的功能。为此:

#include "file" 上的中继,它由 Khronos GLSL 标准定义并被大多数引擎和环境(如 glslViewer、glsl-canvas VS Code 插件、Unity 等)支持。

示例:噪声

cnoise.glsl#include

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform float u_time;

#include "lygia/generative/cnoise.glsl"

void main (void) {
    vec2 st = gl_FragCoord.xy / u_resolution.xy;
    vec3 color = vec3(cnoise(vec3(st * 5.0, u_time)));

    gl_FragColor = vec4(color, 1.0);
}

为了运行这个例子,我使用了 glslViewer

于 2021-06-04T09:52:14.407 回答
0

FWIW 我有同样的问题,我需要在 WebGL 1.0 中实现它,所以我不能使用前面答案中给出的一些例子。我尝试了之前提到的Gold NoisePHI ,但对我来说使用并没有真正点击。 (distance(xy * PHI, xy) * seed只是等于length(xy) * (1.0 - PHI) * seed,所以我看不出PHI当它直接乘以时应该如何发挥魔力seed

无论如何,我做了类似的事情,只是没有PHI,而是在另一个地方添加了一些变化,基本上我将与框架外部的某个随机点tan之间的距离xy乘以右上角,然后将其与另一个这样的随机点distance之间的距离相乘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>

于 2021-09-20T18:52:28.500 回答
0

请参阅下面的示例如何向渲染纹理添加白噪声。解决方案是使用两种纹理:原始和纯白噪声,例如:wiki white noise

private static final String VERTEX_SHADER =
    "uniform mat4 uMVPMatrix;\n" +
    "uniform mat4 uMVMatrix;\n" +
    "uniform mat4 uSTMatrix;\n" +
    "attribute vec4 aPosition;\n" +
    "attribute vec4 aTextureCoord;\n" +
    "varying vec2 vTextureCoord;\n" +
    "varying vec4 vInCamPosition;\n" +
    "void main() {\n" +
    "    vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
    "    gl_Position = uMVPMatrix * aPosition;\n" +
    "}\n";

private static final String FRAGMENT_SHADER =
        "precision mediump float;\n" +
        "uniform sampler2D sTextureUnit;\n" +
        "uniform sampler2D sNoiseTextureUnit;\n" +
        "uniform float uNoseFactor;\n" +
        "varying vec2 vTextureCoord;\n" +
        "varying vec4 vInCamPosition;\n" +
        "void main() {\n" +
                "    gl_FragColor = texture2D(sTextureUnit, vTextureCoord);\n" +
                "    vec4 vRandChosenColor = texture2D(sNoiseTextureUnit, fract(vTextureCoord + uNoseFactor));\n" +
                "    gl_FragColor.r += (0.05 * vRandChosenColor.r);\n" +
                "    gl_FragColor.g += (0.05 * vRandChosenColor.g);\n" +
                "    gl_FragColor.b += (0.05 * vRandChosenColor.b);\n" +
        "}\n";

共享片段包含参数 uNoiseFactor,主应用程序在每次渲染时都会更新该参数:

float noiseValue = (float)(mRand.nextInt() % 1000)/1000;
int noiseFactorUniformHandle = GLES20.glGetUniformLocation( mProgram, "sNoiseTextureUnit");
GLES20.glUniform1f(noiseFactorUniformHandle, noiseFactor);
于 2018-01-03T15:13:11.427 回答