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

目录
相关文章
|
存储 算法 物联网
R-Tree算法:空间索引的高效解决方案
【5月更文挑战第17天】R-Tree是用于多维空间索引的数据结构,常用于地理信息系统、数据库和计算机图形学。它通过分层矩形区域组织数据,支持快速查询。文章介绍了R-Tree的工作原理、应用场景,如地理信息存储和查询,以及Python的`rtree`库实现示例。此外,还讨论了R-Tree的优势(如空间效率和查询性能)与挑战(如实现复杂和内存消耗),以及优化和变种,如R* Tree和STR。R-Tree在机器学习、实时数据分析等领域有广泛应用,并与其他数据结构(如kd-trees和quad-trees)进行比较。未来趋势将聚焦于优化算法、动态适应性和分布式并行计算。
639 1
|
关系型数据库 定位技术 Python
geopandas中拓扑错误的发现诊断与修复
geopandas中拓扑错误的发现诊断与修复
307 6
|
索引 Python
Pandas中的时间序列利器:set_index用法
Pandas中的时间序列利器:set_index用法
640 0
|
IDE 定位技术 开发工具
Mac中IntelliJ IDEA每次打开立刻“意外退出”的解决方法
Mac中IntelliJ IDEA每次打开立刻“意外退出”的解决方法
601 1
|
数据可视化 前端开发 开发工具
Spyder初使用
Spyder初使用
364 0
|
Android开发 iOS开发 流计算
Mac 安卓(Android) 安装 Genymotion 模拟器
Mac 安卓(Android) 安装 Genymotion 模拟器
1372 0
|
定位技术 开发工具 Windows
如何在RTSP/RTMP直播过程中加入SEI扩展数据发送和接收解析
在直播系统中,除了直播音视频之外,有时候还想从主播端发布文本信息等,这些信息可以不通过视频传输通道发送给用户播放端,但如果传输的数据想和视频保持精准同步,那最好的办法就是这些信息和视频数据打包在一起传输,并通过h264 sei方式就可以把数据放入h264 Access Unit中传输。
487 0
|
编解码 算法 计算机视觉
【CV】PIL.Image.save() 保存图片压缩问题
PIL.Image.save() 保存图片压缩问题
|
定位技术 API Python
Python 优雅地利用两点经纬度计算地理空间距离
Calculate the distance (in various units) between two points on Earth using their latitude and longitude.
1655 0
Python 优雅地利用两点经纬度计算地理空间距离
|
存储 分布式计算 Kubernetes
Github 29K Star的开源对象存储方案——Minio入门宝典
对象存储不是什么新技术了,但是从来都没有被替代掉。为什么?在这个大数据发展迅速地时代,数据已经不单单是简单的文本数据了,每天有大量的图片,视频数据产生,在短视频火爆的今天,这个数量还在增加。有数据表明,当今世界产生的数据,有80%是非关系型的。那么,对于图片,视频等数据的分析可以说是大数据与人工智能的未来发展方向之一。
1653 0
Github 29K Star的开源对象存储方案——Minio入门宝典