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

简介: 【视觉基础篇】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>


668c1e85d74448a88a8a5f9058e26501.png

图片融合

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


92a3efe6268e4528958d528794ca1569.png

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

a72e77a633c64bfa85d7977426c985f8.png

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



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


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


设置纹理

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


在 Shader 中使用纹理对象

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


这里直接使用 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>

4b464934de2e4ecdb8c9d0e00f6d5f4c.png


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>

实现效果如下:

image.png



webgl 实现图像合成

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

34a58fcb63fe4e8baa47fb0fac6e2009.png

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



合成效果如下

1674a6fd78914251ae0602e2bafa65a7.png

目录
相关文章
|
7月前
|
算法 Shell 计算机视觉
【特效】对实时动态人脸进行马赛克及贴图马赛克处理及一些拓展
【特效】对实时动态人脸进行马赛克及贴图马赛克处理及一些拓展
166 0
Photoshop - 怎么让画布大小自适应图像大小?
Photoshop - 怎么让画布大小自适应图像大小?
1083 0
Photoshop - 怎么让画布大小自适应图像大小?
|
5月前
|
计算机视觉 Python
图像绘制以及写字
【7月更文挑战第28天】图像绘制以及写字。
35 7
|
5月前
|
前端开发
Canvas绘画之图像合成,图像层叠效果
Canvas绘画之图像合成,图像层叠效果
|
C++ 计算机视觉 Python
C++ VS OpenGL绘制教室三维立体旋转图像
C++ VS OpenGL绘制教室三维立体旋转图像
165 0
C++ VS OpenGL绘制教室三维立体旋转图像
|
机器学习/深度学习 存储 编解码
|
机器学习/深度学习 存储 编解码
|
机器学习/深度学习 存储 编解码
底层视觉与黑白图像上色
底层视觉(即视觉增强)是计算机视觉中的一个分支,它专注于提高图像整体的观看体验。如果 “中高层视觉” 关注的是如何让计算机理解图像中的内容,那么底层视觉则致力于解决图像的清晰度、色彩、时序等各类画质问题。这些问题的出现与拍摄环境、设备等因素有关,而视觉增强技术则旨在修复这些问题,提供更好的视觉观看体验。
|
算法 前端开发 JavaScript
【视觉基础篇】10 # 图形系统如何表示颜色?
【视觉基础篇】10 # 图形系统如何表示颜色?
178 0
【视觉基础篇】10 # 图形系统如何表示颜色?
|
传感器 JSON 数据可视化
【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?
【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?
212 0
【视觉高级篇】22 # 如何用仿射变换来移动和旋转3D物体?