前言

接触过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. 以最大速度将数据迁移至AWS S3存储

    数据上云,已经成为企业数据管理发展的必然趋势. 对于企业而言,数据上云"常态化"的趋势,无疑是一个巨大的技术红利.而数据规模爆发式增长的今天,移动和访问云端数据却成为困扰企业的一大 ...

  2. Fastjson反序列化分析

    依赖 先研究1.2.24版本的,版本高了就有waf了,不过也能绕,高版本以后再说 <dependency> <groupId>com.alibaba</groupId&g ...

  3. JVM—运行时数据区

    JVM-运行时数据区 运行时数据区概述 JVM运行时数据区如下图: 整个JVM构成里面,主要由三部分组成:类加载系统.运行时数据区.执行引擎. 按照线程使用情况和职责分成两大类: 线程独享(程序执行区 ...

  4. #dp#洛谷 3244 [HNOI2015]落忆枫音

    题目 分析 每个有入度的点可以选择任意一个父节点组成一棵树,那么原来的答案就是 \(\prod_{i=2}^ndeg[i]\) 现在多了一条边,如果边的终点是1或者它是一个自环那么可以不用管这条边. ...

  5. #扫描线,线段树#洛谷 3875 [TJOI2010]被污染的河流

    题目 分析 矩阵面积并不是扫描线模板题吗 然后连题目都没仔细看就交了 发现它是一个线段向外扩展一个格子qwq 代码 #include <cstdio> #include <cctyp ...

  6. #矩阵乘法#洛谷 5343 【XR-1】分块

    题目 分析 考虑dp,\(dp[i]=\sum dp[i-j]\) 既然\(j\)很小,那么这显然可以用矩阵乘法优化 代码 #include <cstdio> #include <c ...

  7. 【Kotlin】类和对象

    1 前言 ​ Kotlin 是面向对象编程语言,与 Java 语言类似,都有类.对象.属性.构造函数.成员函数,都有封装.继承.多态三大特性,不同点如下. Java 有静态(static)代码块,Ko ...

  8. Centos 6.4 配置网页服务器

    Centos 6.4 配置网页服务器 (2013-08-08 22:59:09) 转载▼   分类:linux系统 今天值班,在单位找一台电脑安装了Centos 6.4操作系统. 一.安装软件 yum ...

  9. android 当在github下载一个android项目后

    前言 github是一个很好的社区,有时候呢,我们从android下载项目下来无法build,有很多原因,比如说库不见了,或者说自己没有安装过支持的api版本等,会遇到各种问题. 在此总结一下. 问题 ...

  10. C++ 类方法解析:内外定义、参数、访问控制与静态方法详解

    C++ 类方法 类方法,也称为成员函数,是属于类的函数.它们用于操作或查询类数据,并封装在类定义中.类方法可以分为两种类型: 类内定义方法: 直接在类定义内部声明和定义方法. 类外定义方法: 在类定义 ...