前言

接触过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. Oracle ORA-09925

    Error : 30: Read-only file system 造成这个问题的原因大多数是因为非正常关机后导致文件系统受损引起的,在系统重启之后,受损分区就会被Linux自动挂载为只读. 解决办法 ...

  2. verilog设计知识集合(2)

    verilog设计知识集合(2) 1.阻塞与非阻塞 阻塞赋值是存在先后关系的,非阻塞是不存在先后关系的.一般而言,阻塞用于组合逻辑,非阻塞用于时序逻辑(不一定).阻塞的执行时逐步赋值,非阻塞是同步赋值 ...

  3. JDBC复习:创建MySQL数据表

    1 try { 2 conn=JDBCUtil.getConnection(); 3 preparedStatement = conn.prepareStatement(DROP_TABLE_1); ...

  4. 解密数仓的SQL ON ANYWHERE技术

    本文分享自华为云社区<GaussDB DWS的SQL ON ANYWHERE技术解密>,作者:tooooooooooomy. 1. 前言 适用版本:[8.1.1(及以上)] 查询分析是大数 ...

  5. Vue3 Diff 之 patchKeyedChildren 动态示例

    在学习全网学习各路大神的关于Vue3 Diff算法分析文章的时候,一定离不开关键方法 patchKeyedChildren. patchKeyedChildren 处理的场景比较多,大致有 5 个主要 ...

  6. #凸包,闵可夫斯基和#CF87E Mogohu-Rea Idol

    题目 按逆时针顺序给出三个凸包点集 \(\mathbb{A,B,C}\),每次查询给出点 \(D\), 问是否存在点 \(A\in\mathbb{A},B\in\mathbb{B},C\in\math ...

  7. #平衡树,set#洛谷 2286 [HNOI2004]宠物收养场

    题目 分析 由于宠物被领养者领养和领养者领养宠物操作是一样的, 考虑建两棵平衡树维护操作,以领养者领养宠物为例 若当前没有宠物,就将领养者加入平衡树中, 否则选择最接近的特点值的宠物统计答案并删除该宠 ...

  8. JDK10的新特性:本地变量类型var

    目录 简介 为什么我们需要var var使用在什么地方 var不能用在什么地方 其他var的特点 总结 简介 java以面向对象的特性显著于世并得到了蓬勃的发展.在语言的发展过程中,为了让java语言 ...

  9. Python 集合(Sets)2

    访问项 您无法通过引用索引或键来访问集合中的项.但是,您可以使用for循环遍历集合项,或者使用in关键字检查集合中是否存在指定的值. 示例,遍历集合并打印值: thisset = {"app ...

  10. Ubuntu SVN 需要证书及密码验证问题

    问题概览 问题一 Ubuntu 20.04 下使用 SVN ,会报错 SVN 的证书错误,无论是选择接受 t 还是永久接受 p,下次都会要求再次接受:在 kali 或者 Windows 上没有出现该问 ...