前言

接触过Canvas的小伙伴应该都知道,在Canvas2D中我们要加载一个图片很简单,通过调用drawImage API就能将图像绘制到画布上,当然在WebGL中我们也可以绘制图像,在绘制时我们需要用到WebGL中的纹理对象,在之前WebGL实现网格背景的文章中,我使用了一个叫做纹理坐标的配置,现在要完成纹理的加载我们也需要用到纹理坐标,并且我们可以通过对纹理坐标处理实现简单的”马赛克“效果。通过对纹理的使用学习,我感觉自己对纹理坐标的认知,和之前学习网格背景时,有点不一样了,这大概就是学习的过程吧,不断更新自己的认知。

接下来我会介绍纹理的基础使用,并在此基础上实现简单的局部“马赛克”效果。

创建纹理并绑定到上下文

在Shader中使用纹理之前,我们需要先在JavaScript中创建纹理对象。

// 创建纹理对象
const texture = gl.createTexture();
// 坐标翻转
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
// 将纹理绑定到当前上下文
gl.bindTexture(gl.TEXTURE_2D, texture);
// 在图片加载完毕后,指定纹理图像
gl.texImage2D(
gl.TEXTURE_2D,
level,
internalFormat,
srcFormat,
srcType,
image,
);
// 设置纹理的一些参数
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);

上述代码中pixelStorei方法设置了像素存储格式,它的作用是将图片坐标进行翻转,因为GIF、JPEG和PNG图片使用的坐标系统以左上角为原点,X轴水平向右,Y轴垂直向下,而纹理坐标是左下角为原点,Y轴垂直向上,两者Y轴的方向相反,所以为了使图片在WebGL中正常显示,需要这一步操作。

还有一点需要注意的是,在WebGL中使用的图片需要和当前页面同源。

使用纹理

完成纹理的创建后,我们就可以通过纹理单元编号激活指定纹理,来在WebGL中使用。

// 按照单元编号激活纹理
gl.activeTexture(gl.TEXTURE0);
// 将纹理绑定到上下文
gl.bindTexture(gl.TEXTURE_2D, texture);
// 获取Shader中纹理变量
const loc = gl.getUniformLocation(program, "tMap");
// 将对应的纹理单元写入Shader变量
gl.uniform1i(loc, 0);

默认情况下WebGL会使用第一个纹理,所以如果我们只有一个纹理的话,不调用activeTexture方法也能正常使用纹理。

激活纹理后,我们就可以将对应的纹理单元写入Shader变量,这样就可以在Shader中使用纹理了,可以看到这里向Shader传递的并不是纹理对象,而是纹理单元编号。

加载纹理

等到JavaScript传递纹理信息后,Shader就可以使用这个纹理了。

precision mediump float; // 添加如下精度描述
uniform sampler2D tMap; // 纹理相关 varying vec2 vUv; void main() {
gl_FragColor = texture2D(tMap, vUv);
}

在片元着色器中,我们使用sampler2D类型的变量接收纹理信息。

可以看到在GLSL代码中,我们使用了一个叫texture2D的函数,这个函数的作用是根据纹理坐标,从纹理中提取颜色。

通过对纹理的简单使用,我目前的理解是,纹理坐标是与顶点坐标存在一个对应的关系。

// ...
let vertices = new Float32Array([ // 顶点坐标
[-1, -1],
[-1, 1],
[1, 1],
[1, -1]
].flat()),
// ...
let vertices = new Float32Array([ // 纹理坐标
[0, 0],
[0, 1],
[1, 1],
[1, 0]
].flat()),

两组坐标是对应的,所以在顶点着色器中虽然我们只是指定了顶点,但根据对应关系,此时顶点对应的纹理坐标也是已知的。

因为前面对纹理的参数设置(gl.CLAMP_TO_EDGE),纹理是拉伸铺在整个纹理坐标上。(我还没深入学习纹理的参数,这里我只是根据单词意思做的猜测。)

所以就可以通过texture2D函数根据纹理坐标提取到图像对应位置的像素信息了,也就是颜色色值,并将它赋值给gl_FragColor常量、给片元上色。

至此我们就实现了简单的纹理加载,将图像绘制到WebGL的画布上了。

接下去我们就来实现图片的局部“马赛克”效果。

因为对于纹理的具体使用步骤我们已经知道了,所以在接下去的例子中,我就使用课程提供的gl-renderer库来简化纹理的加载使用操作,专注于效果的实现。

实现局部“马赛克”

在处理照片时,我们常常需要将一些敏感的或者是不想展示的信息使用马赛克的效果处理掉,那么在WebGL中我们要怎么去实现呢?

  • 首先我们设置马赛克效果的中心点,对应的是纹理坐标的值。

    renderer.uniforms.center = [-2.0, -2.0];

    初始中心点随意设置一个在0-1之外的位置。

  • 接着设置马赛克的范围。

    const radiusPX = 100;
    renderer.uniforms.radiusX = radiusPX / canvasRef.value.width;
    renderer.uniforms.radiusY = radiusPX / canvasRef.value.height;

    我们将马赛克的半径范围设置为100px,并将它转换为WebGL内对应的数值,使用uniform传递给Shader。

  • 然后我们添加鼠标点击事件的监听。

    const clickHandler = e => {
    e.preventDefault();
    const {width, height} = canvasRef.value.getBoundingClientRect();
    const {offsetX: x, offsetY: y} = e;
    // 转换为纹理坐标上的值
    const center = [];
    center[0] = x / width;
    center[1] = (height - y) / height;
    renderer.uniforms.center = center;
    };

    offsetX和offsetY分别表示鼠标位置距离元素左边和顶部的距离,所以height-y表示鼠标位置距离元素底部多远,通过分别除以宽和高获得鼠标在WebGL纹理坐标上的值。

    这样通过监听鼠标点击事件,我们就可以动态更新马赛克的位置。

  • 完成uniform的传递后,我们就可以在片元着色器中使用了。

    首先我们对片元对应的纹理坐标进行缩放,X坐标放大50倍,Y坐标放大27.7倍,与画布的宽高比例一致,得到50乘以27.7,也就是1350个20x20大小的方格;同时获取到X和Y坐标的整数部分,整数部分相当于片元所在方格在横纵坐标方向的索引。

    vec2 st = vUv * vec2(50, 27.7);
    vec2 uv = floor(st);

    接着根据原始纹理坐标的位置与中心点的距离,我们使用椭圆的公式来判断片元是否在马赛克范围内。(因为画布宽高不一样,所以这里我们用椭圆公式判断。)

    // 中心点坐标
    float x0 = center.x;
    float y0 = center.y; if (pow(abs(vUv.x - x0), 2.0) / pow(radiusX, 2.0) + pow(abs(vUv.y - y0), 2.0) / pow(radiusY, 2.0) <= 1.0) {
    color = texture2D(tMap, vec2(uv.x / 50.0, uv.y / 27.7));
    } else {
    color = texture2D(tMap, vUv);
    }

    如果是在范围内的片元,我们就将方格的索引进行缩放,对应到原始纹理坐标上的值,因为一个方格内所有的片元对应的索引值一致,所以按照这个值提取到的颜色是一样的,也就是一个方格内是一种颜色。

    如果不在马赛克范围内,就按照普通的提取纹理色值的方式。

  • 最后就是将颜色值赋值给常量gl_FragColor,完成了给片元上色。

到这里我们就实现了一个简单的马赛克效果,可以通过点击鼠标给图片指定位置添加马赛克效果,是一个圆形的样子,我们可以通过使用不同的公式判断,呈现不同的马赛克形状,比如正方形。

总结

在WebGL中纹理也是比较重要的内容,可以让我们使用图片,最早我是在JavaScript高级程序设计这本书中接触到纹理的,但是因为书里给出的代码并不完整,并且我当时也没去深入了解,所以当时的代码并没有跑起来,现在我通过学习一个可视化教程才知道说纹理要怎么去用,了解到通过不同参数的设置可以实现不同的纹理表现,呈现不同的视觉效果,本期内容只是简单的纹理使用,对纹理感兴趣的小伙伴可以再自己深入研究。

WebGL实现简易的局部“马赛克”的更多相关文章

  1. OpenCV计算机视觉学习(12)——图像量化处理&图像采样处理(K-Means聚类量化,局部马赛克处理)

    如果需要处理的原图及代码,请移步小编的GitHub地址 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/ComputerVisionPractice 准备 ...

  2. Python图像处理丨带你认识图像量化处理及局部马赛克特效

    摘要:本文主要讲述如何进行图像量化处理和采样处理及局部马赛克特效. 本文分享自华为云社区<[Python图像处理] 二十.图像量化处理和采样处理及局部马赛克特效>,作者: eastmoun ...

  3. Photoshop怎么实现图片局部马赛克

    学好ps是一件很重要的事情,作为日常必备技能,不管是在遇到这样的同时请求帮忙或者老板发配的任务的时候,就能分分钟派上用场了. 1:安装运行photoshop,点击文件-打开,选择要ps的图片. 图片. ...

  4. canva实践小实例 —— 马赛克效果

    前面给大家带来了操作像素的API,此时此刻,我觉得应该配以小实例来进行进一步的说明和演示,以便给大家带来更宽广的视野和灵感,你们看了我的那么多的文章,应该是懂我的风格,废话不多说,进入正题: 这次给大 ...

  5. 提高驾驶技术:用GAN去除(爱情)动作片中的马赛克和衣服

    同步自我的知乎专栏:https://zhuanlan.zhihu.com/p/27199954 作为一名久经片场的老司机,早就想写一些探讨驾驶技术的文章.这篇就介绍利用生成式对抗网络(GAN)的两个基 ...

  6. Three.js使用局部纹理更新

    THREE.js开发的应用运行在iphone5下发现有些时候会崩溃,跟了几天发现是因为Sprite太多频繁更新纹理占用显存导致的.通常解决纹理频繁更新问题就要用到one draw all方法,放到纹理 ...

  7. WebGL文字渲染的那些问题

    THREE.js开发的应用运行在iphone5下发现有些时候会崩溃,跟了几天发现是因为Sprite太多频繁更新纹理占用显存导致的.通常解决纹理频繁更新问题就要用到one draw all方法,放到纹理 ...

  8. DVB数字电视常见信号指标解释

    1. 平均功率与峰值电平       峰值电平在模拟电视广播时用于表征频道信号电平强弱. 模拟电视信号是单极性.不对称的,即电视信号有一个固定黑色参考电平,比黑色亮的信号处在黑色电平线一边,同步脉冲处 ...

  9. php 图片局部打马赛克

    php 图片局部打马赛克 原理: 对图片中选定区域的每一像素,添加若干宽度及高度,生成矩型.而每一像素的矩型重叠在一起.就形成了马赛克效果. 本例使用GD库的imagecolorat获取像素颜色,使用 ...

  10. WebGL简易教程(二):向着色器传输数据

    目录 1. 概述 2. 示例:绘制一个点(改进版) 1) attribute变量 2) uniform变量 3) varying变量 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL ...

随机推荐

  1. read IEEE Standard for verilog(1)

    IEEE Standard for Verilog Hardware Description Language 英语说明阅读,首先看导读.目录.摘要等内容. 摘要: 1 Abstract: The V ...

  2. FCOSv2:原作的扩展版本,小修小改,性能高达50.4AP | IEEE T-PAMI 2020

    本文是对FCOS的小修小改,最终性能达到了50.4AP,可谓相当强劲了,大家在工程上可以参考其中的改进以及提升方法   来源:晓飞的算法工程笔记 公众号 论文: FCOS: A Simple and ...

  3. 可变形卷积系列(二) MSRA提出升级版DCNv2,变形能力更强 | CVPR 2019

    论文提出DCNv2,不仅对DCNv1的结构进行了改进,还使用了有效的蒸馏学习策略,使得性能有很大的提升,各个方面都值得借鉴   来源:晓飞的算法工程笔记 公众号 论文: Deformable Conv ...

  4. Python数据类型---列表、元祖、字典【详解】

    一.列表(List) 1.列表可以用来存储不同的数据类型,使用 [ ] e.g. 1 service = ['http','ssh','ftp'] 2.列表是有索引的,也就是可以通过下标来访问数据 3 ...

  5. #分治NTT#CF1218E Product Tuples

    Codeforces 用 OGF 表示 \(F(B,x)\) 就是 \[\prod_{i=1}^n(1+(q-a_i)x) \] 直接分治 NTT 把 \([x^k]\) 也就是这一位的系数求出来就可 ...

  6. #倍增FFT#CF755G PolandBall and Many Other Balls

    题目 有一排 \(n\) 个球,定义一个组可以只包含一个球或者包含两个相邻的球. 现在一个球只能分到一个组中,求从这些球中取出 \(k\) 组的方案数. \(n\leq 10^9 ,k<2^{1 ...

  7. 4步成功将三方库——speexdsp移植到OpenHarmony

     战码先锋,PR征集令(以下简称"战码先锋")第二期正如火如荼地进行中,涉及OpenAtom OpenHarmony(以下简称"OpenHarmony")主干仓 ...

  8. Go 项目依赖注入wire工具最佳实践介绍与使用

    目录 一.引入 二.控制反转与依赖注入 三.为什么需要依赖注入工具 3.1 示例 3.2 依赖注入写法与非依赖注入写法 四.wire 工具介绍与安装 4.1 wire 基本介绍 4.2 安装 五.Wi ...

  9. Transformer从入门到精通(The Annotated Transformer翻译)

    Transformer从入门到精通(The Annotated Transformer) Attention is All You Need v2022: Austin Huang, Suraj Su ...

  10. MySQL学习路线一条龙

    引言 在当前的IT行业,无论是校园招聘还是社会招聘,MySQL的重要性不言而喻. 面试过程中,MySQL相关的问题经常出现,这不仅因为它是最流行的关系型数据库之一,而且在日常的软件开发中,MySQL的 ...