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

目录
相关文章
|
6月前
|
算法 Shell 计算机视觉
【特效】对实时动态人脸进行马赛克及贴图马赛克处理及一些拓展
【特效】对实时动态人脸进行马赛克及贴图马赛克处理及一些拓展
148 0
Photoshop - 怎么让画布大小自适应图像大小?
Photoshop - 怎么让画布大小自适应图像大小?
1037 0
Photoshop - 怎么让画布大小自适应图像大小?
|
1月前
ThreeJs给物体添加贴图
这篇文章详细说明了在Three.js中如何给3D物体添加贴图,并展示了实现局部贴图的技术和方法。
71 1
ThreeJs给物体添加贴图
|
6月前
|
数据可视化 定位技术 开发者
黑白或彩色线稿地图设计定制装饰画中线条轮廓素材底图获取方法合集
黑白或彩色线稿地图设计定制装饰画中线条轮廓素材底图获取方法合集
|
C++ 计算机视觉 Python
C++ VS OpenGL绘制教室三维立体旋转图像
C++ VS OpenGL绘制教室三维立体旋转图像
128 0
C++ VS OpenGL绘制教室三维立体旋转图像
|
机器学习/深度学习 存储 编解码
|
机器学习/深度学习 存储 编解码
|
机器学习/深度学习 存储 编解码
底层视觉与黑白图像上色
底层视觉(即视觉增强)是计算机视觉中的一个分支,它专注于提高图像整体的观看体验。如果 “中高层视觉” 关注的是如何让计算机理解图像中的内容,那么底层视觉则致力于解决图像的清晰度、色彩、时序等各类画质问题。这些问题的出现与拍摄环境、设备等因素有关,而视觉增强技术则旨在修复这些问题,提供更好的视觉观看体验。
Photoshop极坐标滤镜巧绘三维游泳圈
Photoshop极坐标滤镜巧绘三维游泳圈
80 0
|
数据可视化 算法 数据挖掘
使用Gabor滤镜对图像进行纹理分割
演示如何使用纹理分割根据纹理识别区域。目标是将狗从浴室地板上分开。由于浴室地板的规则、周期性图案与狗皮毛规则、光滑的纹理之间的质地差异,这种分割在视觉上很明显。
161 0