美文网首页shader
日积月累Shader - 11 噪声

日积月累Shader - 11 噪声

作者: Zszen | 来源:发表于2019-06-11 16:46 被阅读0次

    提示

    教程例子都可以到下面网址进行运行,不需要另外安装软件环境:
    官方提供在线编写shader工具:https://thebookofshaders.com/edit.php
    glslsandbox网站:http://glslsandbox.com/
    shadertoy网站:https://www.shadertoy.com/

    Noise 噪声

    我们感知得到浮动在皮肤上的空气,晒在脸上的阳光。世界如此生动而丰富。颜色,质地,声音。当我们走在路上,不免会注意到路面、石头、树木和云朵的表面的样子。

    这些纹理的不可预测性可以叫做“random"(随机),但是它们看起来不像是我们之前玩的 random。{真实世界}是如此的丰富而复杂!我们如何才能才能用计算机模拟这些多样的纹理呢?
    这就是 Ken Perlin 想要解答的问题。在20世纪80年代早期,他被委任为电影 “Tron”(电子世界争霸战)制作现实中的纹理。为了解决这个问题,他想出了一个优雅的算法,且获得了奥斯卡奖(名副其实)。
    下面这个并不是经典的 Perlin noise 算法,但是这是一个理解如何生成 noise 的好的出发点。

    float i = floor(x);  // 整数(i 代表 integer)
    float f = fract(x);  // 小数(f 代表 fraction)
    y = rand(i); //rand() 在之前的章节提过
    // y = mix(rand(i), rand(i + 1.0), f);
    // y = mix(rand(i), rand(i + 1.0), smoothstep(0.,1.,f));
    

    默认生成一个随机数图形


    • 问题是随机数之间没有过度,直接就从另一个随机数变化到了另一个随机数
    • 解决方案:y = mix(rand(i), rand(i + 1.0), f); 将i和i+1之间进行过度,因为每段的百分比就是变量f存储的值,自然可以产生过度


    • 但还是不够平滑,值与值之间的变化不存在平滑过渡。
    • 解决方案:y = mix(rand(i), rand(i + 1.0), smoothstep(0.,1.,f)); 通过这种方式可以得到近似平滑的曲线(真正平滑还要考虑相邻曲线的曲率)


    • 试试把f换成 float u = f * f * (3.0 - 2.0 * f ); 会发现也产生曲线,而且和第三幅图一样,因为smoothstep就是这个公式 🤣

    2D Noise

    现在我们知道了如何在一维使用 noise,是时候进入二维世界了。在 2D 中,除了在一条线的两点(fract(x) 和 fract(x)+1.0)中插值,我们将在一个平面上的方形的四角(fract(st), fract(st)+vec2(1.,0.), fract(st)+vec2(0.,1.) 和 fract(st)+vec2(1.,1.))中插值。



    同样,如果我们想要在三维中使用 noise,就需要在一个立方体的8个角中插值。这个技术的重点在于插入随机值,所以我们叫它 value noise。



    就像一维的那个例子,这个插值不是线性的,而是三次方的,它会平滑地在方形网格中插入点。
    #ifdef GL_ES
    precision mediump float;
    #endif
    
    uniform vec2 u_resolution;
    uniform vec2 u_mouse;
    uniform float u_time;
    
    // 2D Random
    float random (in vec2 st) {
        return fract(sin(dot(st.xy,
                             vec2(12.9898,78.233)))
                     * 43758.5453123);
    }
    
    // 2D Noise based on Morgan McGuire @morgan3d
    // https://www.shadertoy.com/view/4dS3Wd
    float noise (in vec2 st) {
        vec2 i = floor(st);
        vec2 f = fract(st);
    
        // Four corners in 2D of a tile
        float a = random(i);
        float b = random(i + vec2(1.0, 0.0));
        float c = random(i + vec2(0.0, 1.0));
        float d = random(i + vec2(1.0, 1.0));
    
        // Smooth Interpolation
    
        // Cubic Hermine Curve.  Same as SmoothStep()
        vec2 u = f*f*(3.0-2.0*f);
        // u = smoothstep(0.,1.,f);
    
        // Mix 4 coorners percentages
        return mix(a, b, u.x) +
                (c - a)* u.y * (1.0 - u.x) +
                (d - b) * u.x * u.y;
    }
    
    void main() {
        vec2 st = gl_FragCoord.xy/u_resolution.xy;
    
        // Scale the coordinate system to see
        // some noise in action
        vec2 pos = vec2(st*5.0);
    
        // Use the noise function
        float n = noise(pos);
    
        gl_FragColor = vec4(vec2(n),.5, 1.0);
    }
    

    我们先把空间大小变成五倍(第 45 行)以便看清栅格间的插值。然后在 noise 函数中我们把空间分成更小的单元。我们把它的整数部分和非整数部分都储存在这个单元里。我们计算整数位置的顶点的坐标,并给每个顶点生成一个随机值(第 23 - 26 行)。最后,在第 35 行用我们之前储存的小数位置的值,在四个顶点的随机值之间插值。

    • 每个被分割的格子,四个顶点以及四条公共边获得的随机值是相同的,可被近似平滑

    生成式设计中的 noise 应用

    Noise 算法的设计初衷是将难以言说的自然质感转化成数字图像。在目前我们看到的一维和二维的实践中,都是在random values(随机值)之间插值,所以它们才被叫做 Value Noise,但是还有很多很多获取 noise 的方法……


    https://thebookofshaders.com/edit.php#11/2d-vnoise.frag

    如你所见,value noise 看起来非常“块状”。为了消除这种块状的效果,在 1985 年 Ken Perlin 开发了另一种 noise 算法 Gradient Noise。Ken 解决了如何插入随机的 gradients(梯度、渐变)而不是一个固定值。这些梯度值来自于一个二维的随机函数,返回一个方向(vec2 格式的向量),而不仅是一个值(float格式)。点击下面的图片查看代码,看这个函数是如何运作的。


    https://thebookofshaders.com/edit.php#11/2d-gnoise.frag

    花一分钟来看看 Inigo Quilez 做的两个例子,注意 value noisegradient noise的区别。

    就像一个画家非常了解画上的颜料是如何晕染的,我们越了解 noise 是如何运作的,越能更好地使用 noise。比如,如果我们要用一个二维的 noise 来旋转空间中的直线,我们就可以制作下图的旋涡状效果,看起来就像木头表皮一样。同样地,你可以点击图片查看代码。
    https://thebookofshaders.com/edit.php#11/wood.frag

    mix( mix( random( i + vec2(0.0,0.0) ),
                         random( i + vec2(1.0,0.0) ), u.x),
                    mix( random( i + vec2(0.0,1.0) ),
                         random( i + vec2(1.0,1.0) ), u.x), u.y);
    
    • 先对顶部和底部进行水平插值, 在对这两条线进行垂直插值, 就得到了连续的扭曲~~ 精神污染,脑壳疼

    另一种用 noise 制作有趣的图案的方式是用 distance field(距离场)处理它,用用 第七章提到的招数。

    https://thebookofshaders.com/edit.php#11/splatter.frag

        color += smoothstep(.15,.2,noise(st*10.)); // 黑色的泼溅点
        color -= smoothstep(.35,.4,noise(st*10.)); // 泼溅点上的洞
    

    第三种方法是用 noise 函数来变换一个形状。这个也需要我们在第七章学到的技术。
    https://thebookofshaders.com/edit.php#11/circleWave-noise.frag

    Simplex Noise

    对于 Ken Perlin 来说他的算法所取得的成功是远远不够的。他觉得可以更好。在 2001 年的 Siggraph(译者注:Siggraph是由美国计算机协会{计算机图形专业组}组织的计算机图形学顶级年度会议)上,他展示了 “simplex noise”,simplex noise 比之前的算法有如下优化:

    • 它有着更低的计算复杂度和更少乘法计算。
    • 它可以用更少的计算量达到更高的维度。
    • 制造出的 noise 没有明显的人工痕迹。
    • 有着定义得很精巧的连续的 gradients(梯度),可以大大降低计算成本。
    • 特别易于硬件实现。

    我们已经知道在二维中他是如何在四个点(正方形的四个角)之间插值的;所以没错你已经猜到了,对于三维(这里有个示例)和四维我们需要插入 8 个和 16 个点。对吧?也就是说对于 N 维你需要插入 2 的 n 次方个点(2^N)。但是 Ken 很聪明地意识到尽管很显然填充屏幕的形状应该是方形,在二维中最简单的形状却是等边三角形。所以他把正方形网格(我们才刚学了怎么用)替换成了单纯形等边三角形的网格。

    这时 N 维的形状就只需要 N + 1 个点了。也就是说在二维中少了 1 个点,三维中少了 4 个,四维中则少了 11 个!巨大的提升!
    在二维中插值过程和常规的 noise 差不多,通过在一组点之间插值。但是在这种情况下,改用单纯形网格,我们只需要给总共 3 个点插值。

    这个单纯形网格是如何制作的?这是另一个聪明绝顶而十分优雅的做法。可以先把常规的四角网格分成两个等腰三角形,然后再把三角形歪斜成等边三角形。

    另一个 Simplex Noise 的优化是把三次 Hermite 函数(Cubic Hermite Curve:f(x) = 3x2-2x3,和 smoothstep() 一样)替换成了四次 Hermite 函数( f(x) = 6x5-15x4+10x^3 )。这就使得函数曲线两端更“平”,所以每个格的边缘更加优雅地与另一个衔接。也就是说格子的过渡更加连续。你可以取消下面例子的第二个公式的注释,亲眼看看其中的变化(或者看这个例子)。

    // 三次 Hermite 曲线。和 SmoothStep() 一样
    y = x*x*(3.0-2.0*x);
    
    // 四次 Hermite 曲线
    y = x*x*x*(x*(x*6.-15.)+10.);
    

    所有这些进展汇聚成了算法中的杰作 Simplex Noise。下面是这个算法在 GLSL 中的应用,作者是 Ian McEwan,以这篇论文发表,对于我们的教学而言太复杂了,但你可以点开看看,也许没有你想象得那么晦涩难懂。
    https://thebookofshaders.com/edit.php#11/2d-snoise-clear.frag

    相关文章

      网友评论

        本文标题:日积月累Shader - 11 噪声

        本文链接:https://www.haomeiwen.com/subject/qalqfctx.html