• 【视觉基础篇】13 # 如何给简单的图案添加纹理和复杂滤镜?


    说明

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

    边缘模糊

    在遍历像素点的时候,同时计算当前像素点到图片中心点的距离,然后根据距离设置透明度,就可以实现边缘模糊的效果。

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>图片边缘模糊的效果title>
        head>
        <body>
            <img
                src="https://images.unsplash.com/photo-1666552982368-dd0e2bb96993?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80"
                alt=""
            />
            <canvas id="paper" width="0" height="0">canvas>
            <script type="module">
                import {
                    loadImage,
                    getImageData,
                    traverse,
                } from "./common/lib/util.js";
    
                const canvas = document.getElementById("paper");
                const context = canvas.getContext("2d");
    
                (async function () {
                    // 异步加载图片
                    const img = await loadImage(
                        "https://images.unsplash.com/photo-1666552982368-dd0e2bb96993?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2070&q=80"
                    );
                    // 获取图片的 imageData 数据对象
                    const imageData = getImageData(img);
                    console.log("imageData---->", imageData);
                    // 遍历 imageData 数据对象:traverse 函数会自动遍历图片的每个像素点,把获得的像素信息传给参数中的回调函数处理
                    traverse(imageData, ({r, g, b, a, x, y}) => {
                        const d = Math.hypot((x - 0.5), (y - 0.5));
                        a *= 1.0 - 2 * d;
                        return [r, g, b, a];
                    });
                    // 更新canvas内容
                    canvas.width = imageData.width;
                    canvas.height = imageData.height;
                    // 将数据从已有的 ImageData 对象绘制到位图
                    context.putImageData(imageData, 0, 0);
                })();
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    在这里插入图片描述

    图片融合

    给一张照片加上阳光照耀的效果。具体操作就是,把下面的透明的图片叠加到一张照片上。这种能叠加到其他照片上的图片,通常被称为纹理Texture),叠加后的效果也叫做纹理效果。

    在这里插入图片描述

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>纹理与图片叠加title>
        head>
        <body>
            <img
                src="./assets/img/flower.jpg"
                alt=""
            />
            <canvas id="paper" width="0" height="0">canvas>
            <script type="module">
                import {
                    loadImage,
                    getImageData,
                    traverse,
                    getPixel,
                } from "./common/lib/util.js";
                import {
                    transformColor,
                    brightness,
                    saturate,
                } from "./common/lib/color-matrix.js";
    
                const canvas = document.getElementById("paper");
                const context = canvas.getContext("2d");
    
                (async function () {
                    // 异步加载图片
                    const img = await loadImage(
                        "./assets/img/flower.jpg"
                    );
                    // 阳光效果图
                    const sunlight = await loadImage(
                        "./assets/img/sunlight-texture.png"
                    );
                    // 获取图片的 imageData 数据对象
                    const imageData = getImageData(img);
                    console.log("imageData---->", imageData);
                    const texture = getImageData(sunlight);
                    console.log("texture---->", texture);
                    // 遍历 imageData 数据对象:traverse 函数会自动遍历图片的每个像素点,把获得的像素信息传给参数中的回调函数处理
                    traverse(imageData, ({ r, g, b, a, index }) => {
                        const texColor = getPixel(texture, index);
                        return transformColor(
                            [r, g, b, a],
                            brightness(1 + 0.7 * texColor[3]),
                            saturate(2 - texColor[3])
                        );
                    });
                    // 更新canvas内容
                    canvas.width = imageData.width;
                    canvas.height = imageData.height;
                    // 将数据从已有的 ImageData 对象绘制到位图
                    context.putImageData(imageData, 0, 0);
                })();
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    在这里插入图片描述

    弊端:必须循环遍历图片上的每个像素点,图片一大计算量很大。

    片元着色器是怎么处理像素的?

    WebGL 中,先通过图片或者 Canvas 对象来创建纹理对象,纹理对象包括了整张图片的所有像素点的颜色信息,然后通过 uniform 传递给着色器,再通过纹理坐标 vUv 来读取对应的具体坐标处像素的颜色信息。

    纹理坐标是一个变量,类型是二维向量,x、y 的值从 0 到 1。

    webgl 实现滤镜

    创建纹理对象

    function createTexture(gl, img) {
        // 创建纹理对象
        const texture = gl.createTexture();
        // 设置预处理函数,由于图片坐标系和WebGL坐标的Y轴是反的,这个设置可以将图片Y坐标翻转一下
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
        // 激活指定纹理单元,WebGL有多个纹理单元,因此在Shader中可以使用多个纹理
        gl.activeTexture(gl.TEXTURE0);
        // 将纹理绑定到当前上下文
        gl.bindTexture(gl.TEXTURE_2D, texture);
        // 指定纹理图像
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
        // 设置纹理的一些参数
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        // 解除纹理绑定
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    设置纹理

    function setTexture(gl, idx) {
        // 激活纹理单元
        gl.activeTexture(gl.TEXTURE0 + idx);
        // 绑定纹理
        gl.bindTexture(gl.TEXTURE_2D, texture);
        // 获取shader中纹理变量
        const loc = gl.getUniformLocation(program, 'tMap');
        // 将对应的纹理单元写入shader变量
        gl.uniform1i(loc, idx);
        // 解除纹理绑定
        gl.bindTexture(gl.TEXTURE_2D, null);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在 Shader 中使用纹理对象

    uniform sampler2D tMap;
    ...
    // 从纹理中提取颜色,vUv是纹理坐标
    vec3 color = texture2D(tMap, vUv);
    
    • 1
    • 2
    • 3
    • 4

    这里直接使用 gl-renderer

    DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>webgl 实现滤镜title>
    head>
    
    <body>
        <canvas width="1920" height="1080">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
    
                uniform sampler2D tMap;
                uniform mat4 colorMatrix;
                varying vec2 vUv;
    
                void main() {
                    vec4 color = texture2D(tMap, vUv);
                    gl_FragColor = colorMatrix * vec4(color.rgb, 1.0);
                    gl_FragColor.a = color.a;
                }
            `;
    
            const canvas = document.querySelector('canvas');
            const renderer = new GlRenderer(canvas);
            // load fragment shader and createProgram
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
    
            (async function () {
                const texture = await renderer.loadTexture('./assets/img/flower.jpg');
                renderer.uniforms.tMap = texture;
                const r = 0.2126,
                    g = 0.7152,
                    b = 0.0722;
    
                renderer.uniforms.colorMatrix = [
                    r, r, r, 0,
                    g, g, g, 0,
                    b, b, b, 0,
                    0, 0, 0, 1,
                ];
    
                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>
        script>
    body>
    
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    在这里插入图片描述

    webgl 实现图片的粒子化

    将图形网格化,因为原始图像的图片像素宽高是 1920px 和 1080px,所以我们用 vec2 st = vUv * vec2(192, 108) 就可以得到 10px X 10px 大小的网格。

    为了取出来的颜色是一个乱序的色值。可以使用伪随机函数 random 根据网格随机一个偏移量,因为这个偏移量是 0~1 之间的值,我们将它乘以 2 再用 1 减去它,就能得到一个范围在 -1~1 之间的随机偏移。

    DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>webgl 实现图片的粒子化title>
    head>
    
    <body>
        <canvas width="1920" height="1080">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
    
                uniform sampler2D tMap;
                uniform float uTime;
                varying vec2 vUv;
    
                float random (vec2 st) {
                    return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123);
                }
    
                void main() {
                    vec2 st = vUv * vec2(192, 108);
                    vec2 uv = vUv + 1.0 - 2.0 * random(floor(st));
                    vec4 color = texture2D(tMap, mix(uv, vUv, min(uTime, 1.0)));
                    gl_FragColor.rgb = color.rgb;
                    gl_FragColor.a = color.a * uTime;
                }
            `;
    
            const canvas = document.querySelector('canvas');
            const renderer = new GlRenderer(canvas);
            // load fragment shader and createProgram
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
    
            (async function () {
                const texture = await renderer.loadTexture('./assets/img/flower.jpg');
                renderer.uniforms.tMap = texture;
                renderer.uniforms.uTime = 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 / 5000;
                    requestAnimationFrame(update);
                }
    
                update(0);
            }());
        script>
        script>
    body>
    
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    实现效果如下:

    在这里插入图片描述

    webgl 实现图像合成

    使用 shader 技术可以把绿幕图片合成到其他图片上

    在这里插入图片描述

    DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>webgl 实现图像合成title>
    head>
    
    <body>
        <canvas width="1920" height="1080">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
    
                uniform sampler2D tMap;
                uniform sampler2D tCat;
                varying vec2 vUv;
    
                void main() {
                    vec4 color = texture2D(tMap, vUv);
                    vec2 st = vUv * 3.0 - vec2(1.2, 0.5);
                    vec4 cat = texture2D(tCat, st);
    
                    gl_FragColor.rgb = cat.rgb;
                    if(cat.r < 0.5 && cat.g > 0.6) {
                        gl_FragColor.rgb = color.rgb;
                    }
                    gl_FragColor.a = color.a;
                }
            `;
    
            const canvas = document.querySelector('canvas');
            const renderer = new GlRenderer(canvas);
            // load fragment shader and createProgram
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);
    
            (async function () {
                const [picture, cat] = await Promise.all([
                    renderer.loadTexture('./assets/img/flower.jpg'),
                    renderer.loadTexture('./assets/img/cat.png'),
                ]);
                renderer.uniforms.tMap = picture;
                renderer.uniforms.tCat = cat;
    
                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>
        script>
    body>
    
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    合成效果如下

    在这里插入图片描述

  • 相关阅读:
    在windows10 安装子系统linux(WSL安装方式)
    Leecode-SQL 1527. 模糊查询匹配(模糊查询用法)
    xLua Lua访问C#注意事项(七)
    MQ 消息丢失、重复、积压问题,如何解决?
    连锁快餐绩效考核中的神秘顾客调查
    VTK学习笔记(十八)指数余弦函数的可视化
    element-ui+vue上传图片和评论现成完整html页面
    央行副行长:比特币是加密资产,是一种投资工具或替代性投资 2021-04-20
    RowHammer 攻击:内存的隐形威胁
    一个 Java 对象到底有多大?
  • 原文地址:https://blog.csdn.net/kaimo313/article/details/126834067