【视觉基础篇】16 # 如何使用噪声生成复杂的纹理?

简介: 【视觉基础篇】16 # 如何使用噪声生成复杂的纹理?

说明

【跟月影学可视化】学习笔记。



什么是噪声?

物理学上,噪声指一切不规则的信号(不一定要是声音),比如电磁噪声,热噪声,无线电传输时的噪声,激光器噪声,光纤通信噪声,照相机拍摄图片时画面的噪声等。



如何实现噪声函数?

我们知道随机数是离散的,如果对离散的随机点进行插值,可以让每个点之间的值连续过渡,然后使用 smoothstep 或者平滑的三次样条来插值,就可以形成一条连续平滑的随机曲线。


对离散的随机值进行插值又被称为插值噪声(Value Noise)。缺点:它的值的梯度不均匀。最直观的表现就是,二维噪声图像有明显的“块状”特点,不够平滑。


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>如何实现噪声函数</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                // 随机函数
                float random (float x) {
                    return fract(sin(x * 1243758.5453123));
                }
                void main() {
                    vec2 st = vUv - vec2(0.5);
                    st *= 10.0;
                    float i = floor(st.x);
                    float f = fract(st.x);
                    // d直接等于随机函数返回值,这样d不连续
                    // float d = random(i);
                    // 线段的首尾就会连起来,得到一段连续的折线。
                    // float d = mix(random(i), random(i + 1.0), f);
                    // 下面两种都得到一条连续并且平滑的曲线
                    // float d = mix(random(i), random(i + 1.0), smoothstep(0.0, 1.0, f));
                    float d = mix(random(i), random(i + 1.0), f * f * (3.0 - 2.0 * f));
                    gl_FragColor.rgb = (smoothstep(st.y - 0.05, st.y, d) - smoothstep(st.y, st.y + 0.05, d)) * vec3(1.0);
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
        </script>
    </body>
</html>

8ee43b0b6ebd46839f2187cb74f13aae.png


在 2D 中,除了在一条线的两点(fract(x) 和 fract(x)+1.0)中插值,我们将在一个平面上的方形的四角(fract(st), fract(st)+vec2(1.,0.), fract(st)+vec2(0.,1.) 和 fract(st)+vec2(1.,1.))中插值。https://thebookofshaders.com/11/?lan=ch


8b447f2e60f74d6ba5ac722470297b9f.png


把 st 与方形区域的四个顶点(对应四个向量)做插值,这样就能得到二维噪声。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>二维噪声</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                float random (vec2 st) {
                    return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123);
                }
                // 二维噪声,对st与方形区域的四个顶点插值
                highp float noise(vec2 st) {
                    vec2 i = floor(st);
                    vec2 f = fract(st);
                    vec2 u = f * f * (3.0 - 2.0 * f);
                    return 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);
                }
                void main() {
                    vec2 st = vUv * 20.0;
                    gl_FragColor.rgb = vec3(noise(st));
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
        </script>
    </body>
</html>

e9b44c61bb464fb18007dbcfc43cd690.png


噪声的应用


实现类似于水滴滚过物体表面的效果

结合噪声和距离场,来实现类似于水滴滚过物体表面的效果。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>实现类似于水滴滚过物体表面的效果</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                uniform float uTime;
                float random (vec2 st) {
                    return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123);
                }
                highp float noise(vec2 st) {
                    vec2 i = floor(st);
                    vec2 f = fract(st);
                    vec2 u = f * f * (3.0 - 2.0 * f);
                    return 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);
                }
                void main() {
                    vec2 st = mix(vec2(-10, -10), vec2(10, 10), vUv);
                    float d = distance(st, vec2(0));
                    d *= noise(uTime + st);
                    d = smoothstep(0.0, 1.0, d) - step(1.0, d);
                    gl_FragColor.rgb = vec3(d);
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
            function update(t) {
                renderer.uniforms.uTime = t / 1000;
                requestAnimationFrame(update);
            }
            update(0);
        </script>
    </body>
</html>


image.gif



实现类似于木头的条纹

使用不同的距离场构造方式,加上旋转噪声,构造出类似于木头的条纹。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>实现类似于木头的条纹</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                uniform float uTime;
                float random (vec2 st) {
                    return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123);
                }
                highp float noise(vec2 st) {
                    vec2 i = floor(st);
                    vec2 f = fract(st);
                    vec2 u = f * f * (3.0 - 2.0 * f);
                    return 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);
                }
                float lines(in vec2 pos, float b){
                    float scale = 10.0;
                    pos *= scale;
                    return smoothstep(0.0, 0.5 + b * 0.5, abs((sin(pos.x * 3.1415) + b * 2.0)) * 0.5);
                }
                vec2 rotate(vec2 v0, float ang) {
                    float sinA = sin(ang);
                    float cosA = cos(ang);
                    mat3 m = mat3(cosA, -sinA, 0, sinA, cosA, 0, 0, 0, 1);
                    return (m * vec3(v0, 1.0)).xy;
                }
                void main() {
                    vec2 st = vUv.yx * vec2(10.0, 3.0);
                    st = rotate(st, noise(st));
                    float d = lines(st, 0.5);
                    gl_FragColor.rgb = 1.0 - vec3(d);
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
            function update(t) {
                renderer.uniforms.uTime = t / 1000;
                requestAnimationFrame(update);
            }
            update(0);
        </script>
    </body>
</html>

8e20d2df2ddf4730999ea7ccdda07719.png



梯度噪声

插值噪声的缺点可以使用另一种噪声算法来解决,梯度噪声是对随机的二维向量来插值,而不是一维的随机数。这样我们就能够获得更加平滑的噪声效果。

可以参考这个例子:https://www.shadertoy.com/view/XdXGW8

54387e63842147808b65b5383801c1a3.png

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>梯度噪声</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                vec2 random2(vec2 st){
                    st = vec2( dot(st,vec2(127.1,311.7)), dot(st,vec2(269.5,183.3)) );
                    return -1.0 + 2.0 * fract(sin(st) * 43758.5453123);
                }
                // Gradient Noise by Inigo Quilez - iq/2013
                // https://www.shadertoy.com/view/XdXGW8
                float noise(vec2 st) {
                    vec2 i = floor(st);
                    vec2 f = fract(st);
                    vec2 u = f * f * (3.0 - 2.0 * f);
                    return mix( mix( dot( random2(i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ),
                        dot( random2(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),
                        mix( dot( random2(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ),
                        dot( random2(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y
                    );
                }
                void main() {
                    vec2 st = vUv * 20.0;
                    gl_FragColor.rgb = vec3(0.5 * noise(st) + 0.5);
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
        </script>
    </body>
</html>

b6399d94f6664c17b698cd722743a5fa.png


用噪声实现云雾效果

Smooth HSV :https://www.shadertoy.com/view/MsS3Wc

32e6a47dd22e4cd8a71c3699ed09df5c.png



<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>用噪声实现云雾效果</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                //  Function from Iñigo Quiles
                //  https://www.shadertoy.com/view/MsS3Wc
                vec3 hsb2rgb(vec3 c){
                    vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 6.0)-3.0)-1.0, 0.0, 1.0);
                    rgb = rgb * rgb * (3.0 - 2.0 * rgb);
                    return c.z * mix(vec3(1.0), rgb, c.y);
                }
                float random (vec2 st) {
                    return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123);
                }
                highp float noise(vec2 st) {
                    vec2 i = floor(st);
                    vec2 f = fract(st);
                    vec2 u = f * f * (3.0 - 2.0 * f);
                    return 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
                    );
                }
                #define OCTAVES 6
                float mist(vec2 st) {
                    //Initial values
                    float value = 0.0;
                    float amplitude = 0.5;
                    // Loop of octaves
                    for(int i = 0; i < OCTAVES; i++) {
                        value += amplitude * noise(st);
                        st *= 2.0;
                        amplitude *= 0.5;
                    }
                    return value;
                }
                uniform float uTime;
                void main() {
                    vec2 st = vUv;
                    st.x += 0.1 * uTime; 
                    // gl_FragColor.rgb = vec3(mist(st));
                    gl_FragColor.rgb = hsb2rgb(vec3 (mist(st), 1.0, 1.0));
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
            renderer.uniforms.uTime = 0.0;
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
            function update(t) {
                renderer.uniforms.uTime = t / 1000;
                requestAnimationFrame(update);
            }
            update(0);
        </script>
    </body>
</html>


网络异常,图片无法展示
|


Simplex Noise


Simplex Noise 是 Ken Perlin 在 2001 年的 Siggraph 会议上展示的 Simplex Noise 算法。它有更低的计算复杂度和更少的乘法运算,并且可以用更少的计算量达到更高的维度,而且它制造出的噪声非常自然。


Simplex Noise 与插值噪声以及梯度噪声的不同之处在于,它不是对四边形进行插值,而是对三角网格进行插值。


如下图:

a0147e4061ff4a738c02200d4474e31b.png


该算法的优点:


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


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Simplex Noise</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
                vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
                vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
                //
                // Description : GLSL 2D simplex noise function
                //      Author : Ian McEwan, Ashima Arts
                //  Maintainer : ijm
                //     Lastmod : 20110822 (ijm)
                //     License :
                //  Copyright (C) 2011 Ashima Arts. All rights reserved.
                //  Distributed under the MIT License. See LICENSE file.
                //  https://github.com/ashima/webgl-noise
                //
                float noise(vec2 v) {
                    // Precompute values for skewed triangular grid
                    const vec4 C = vec4(0.211324865405187,
                        // (3.0-sqrt(3.0))/6.0
                        0.366025403784439,
                        // 0.5*(sqrt(3.0)-1.0)
                        -0.577350269189626,
                        // -1.0 + 2.0 * C.x
                    0.024390243902439);
                    // 1.0 / 41.0
                    // First corner (x0)
                    vec2 i  = floor(v + dot(v, C.yy));
                    vec2 x0 = v - i + dot(i, C.xx);
                    // Other two corners (x1, x2)
                    vec2 i1 = vec2(0.0);
                    i1 = (x0.x > x0.y)? vec2(1.0, 0.0):vec2(0.0, 1.0);
                    vec2 x1 = x0.xy + C.xx - i1;
                    vec2 x2 = x0.xy + C.zz;
                    // Do some permutations to avoid
                    // truncation effects in permutation
                    i = mod289(i);
                    vec3 p = permute(permute( i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0 ));
                    vec3 m = max(0.5 - vec3(
                        dot(x0,x0),
                        dot(x1,x1),
                        dot(x2,x2)
                    ), 0.0);
                    m = m*m ;
                    m = m*m ;
                    // Gradients:
                    //  41 pts uniformly over a line, mapped onto a diamond
                    //  The ring size 17*17 = 289 is close to a multiple
                    //      of 41 (41*7 = 287)
                    vec3 x = 2.0 * fract(p * C.www) - 1.0;
                    vec3 h = abs(x) - 0.5;
                    vec3 ox = floor(x + 0.5);
                    vec3 a0 = x - ox;
                    // Normalise gradients implicitly by scaling m
                    // Approximation of: m *= inversesqrt(a0*a0 + h*h);
                    m *= 1.79284291400159 - 0.85373472095314 * (a0*a0+h*h);
                    // Compute final noise value at P
                    vec3 g = vec3(0.0);
                    g.x  = a0.x  * x0.x  + h.x  * x0.y;
                    g.yz = a0.yz * vec2(x1.x,x2.x) + h.yz * vec2(x1.y,x2.y);
                    return 130.0 * dot(m, g);
                }
                void main() {
                    vec2 st = vUv * 20.0;
                    gl_FragColor.rgb = vec3(0.5 * noise(st) + 0.5);
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
        </script>
    </body>
</html>

57397561e23d49a4a488465aabc678a8.png


网格噪声

网格噪声就是将噪声与网格结合使用的一种纹理生成技术。目前被广泛应用的程序化纹理技术,用来生成随机网格类的视觉效果,可以用来模拟物体表面的晶格、晶体生长、细胞、微生物等等有趣的效果。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>网格噪声</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                uniform float uTime;
                vec2 random2(vec2 st){
                    st = vec2( dot(st,vec2(127.1,311.7)), dot(st,vec2(269.5,183.3)) );
                    return fract(sin(st) * 43758.5453123);
                }
                void main() {
                    vec2 st = vUv * 10.0;
                    float d = 1.0;
                    vec2 i_st = floor(st);
                    vec2 f_st = fract(st);
                    vec2 p = random2(i_st);
                    d = distance(f_st, p);
                    gl_FragColor.rgb = vec3(d);
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
        </script>
    </body>
</html>


image.png


上面每个网格是独立的,并且界限分明,可以计算特征点到当前网格的距离,以及计算它到周围相邻的 8 个网格的距离,然后取最小值去实现边界过渡更圆滑效果。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>网格噪声</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            // 圆滑版本
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                uniform float uTime;
                vec2 random2(vec2 st){
                    st = vec2(dot(st,vec2(127.1,311.7)), dot(st,vec2(269.5,183.3)));
                    return fract(sin(st) * 43758.5453123);
                }
                void main() {
                    vec2 st = vUv * 10.0;
                    float d = 1.0;
                    vec2 i_st = floor(st);
                    vec2 f_st = fract(st);
                    for(int i = -1; i <= 1; i++) {
                        for(int j = -1; j <= 1; j++) {
                            vec2 neighbor = vec2(float(i), float(j));
                            vec2 p = random2(i_st + neighbor);
                            p = 0.5 + 0.5 * sin(uTime + 6.2831 * p);
                            d = min(d, distance(f_st, neighbor + p));
                        }
                    }
                    gl_FragColor.rgb = vec3(d) + step(d, 0.03);
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
      renderer.uniforms.uTime = 0.0;
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
        </script>
    </body>
</html>

c1729e436bd14652bab81d64e7824b8f.png


基于这个圆滑版本我们实现一下细胞动画效果

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>网格噪声模拟生物细胞</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;
                varying vec2 vUv;
                void main() {
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                }
            `;
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif
                varying vec2 vUv;
                uniform float uTime;
                vec2 random2(vec2 st){
                    st = vec2(dot(st,vec2(127.1,311.7)), dot(st,vec2(269.5,183.3)));
                    return fract(sin(st) * 43758.5453123);
                }
                void main() {
                    vec2 st = vUv * 10.0;
                    float d = 1.0;
                    vec2 i_st = floor(st);
                    vec2 f_st = fract(st);
                    for(int i = -1; i <= 1; i++) {
                        for(int j = -1; j <= 1; j++) {
                            vec2 neighbor = vec2(float(i), float(j));
                            vec2 p = random2(i_st + neighbor);
                            p = 0.5 + 0.5 * sin(uTime + 6.2831 * p);
                            d = min(d, distance(f_st, neighbor + p));
                        }
                    }
                    gl_FragColor.rgb = vec3(d) + step(d, 0.03);
                    gl_FragColor.a = 1.0;
                }
            `;
            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
            renderer.uniforms.uTime = 0.0;
            renderer.setMeshData([
                {
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: {
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    },
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                },
            ]);
            renderer.render();
            function update(t) {
                renderer.uniforms.uTime = t / 1000;
                requestAnimationFrame(update);
            }
            update(0);
        </script>
    </body>
</html>

image.png



拓展

网格噪声(Cellular Noise):https://thebookofshaders.com/12/?lan=ch

演示例子:https://thebookofshaders.com/edit.php#12/vorono-01.frag


09abccbb3e204811b1c2f5aab134c15d.png



目录
相关文章
|
2月前
如何使用FabricJS为图像添加平滑处理?
在本文中,我们将展示如何使用FabricJS为图像添加平滑效果。
20 2
|
4月前
|
算法
基于小波变换的图像自适应增强算法
基于小波变换的图像自适应增强算法
20 0
|
7月前
|
监控 算法 数据库
入门了解——三维人脸数据的优点
入门了解——三维人脸数据的优点
59 0
|
机器学习/深度学习 存储 编解码
|
机器学习/深度学习 存储 编解码
|
机器学习/深度学习 存储 编解码
底层视觉与黑白图像上色
底层视觉(即视觉增强)是计算机视觉中的一个分支,它专注于提高图像整体的观看体验。如果 “中高层视觉” 关注的是如何让计算机理解图像中的内容,那么底层视觉则致力于解决图像的清晰度、色彩、时序等各类画质问题。这些问题的出现与拍摄环境、设备等因素有关,而视觉增强技术则旨在修复这些问题,提供更好的视觉观看体验。
|
机器学习/深度学习 算法 数据挖掘
传统图像处理之颜色特征
传统图像处理之颜色特征
265 0
传统图像处理之颜色特征
|
机器学习/深度学习 监控 算法
传统图像处理之相机模型
传统图像处理之相机模型
266 0
|
传感器 JSON 数据可视化
【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?
【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?
212 0
【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?
|
数据可视化 API
【视觉高级篇】21 # 如何添加相机,用透视原理对物体进行投影?
【视觉高级篇】21 # 如何添加相机,用透视原理对物体进行投影?
204 0
【视觉高级篇】21 # 如何添加相机,用透视原理对物体进行投影?