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

Posted 凯小默

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了视觉基础篇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>

在 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

把 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>

噪声的应用

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

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

<!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>

实现类似于木头的条纹

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

<!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>

梯度噪声

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

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

<!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 Ini

以上是关于视觉基础篇16 # 如何使用噪声生成复杂的纹理?的主要内容,如果未能解决你的问题,请参考以下文章

视觉基础篇14 # 如何使用片元着色器进行几何造型?

视觉基础篇11 # 图案生成:如何生成重复图案分形图案以及随机效果?

WebGL进阶——走进图形噪声

图形学中的噪声入门

柏林噪声的每顶点法线?

学习笔记计算机视觉与深度学习(3.卷积与图像去噪/边缘提取/纹理表示)