WebGL实现简易的局部“马赛克”

webgl · 浏览次数 : 0

小编点评

## 纹理基础使用与局部马赛克效果实现 该文深入介绍了纹理的使用以及实现局部马赛克效果的步骤,并使用gl-renderer库简化了纹理加载过程。 **纹理基础使用:** 1. **引入纹理:** ```javascript const texture = renderer.getContext('2d').createTexture(); ``` 2. **加载纹理:** ```javascript texture.image = img; // img为图片对象 texture.minFilter = gl.LINEAR; ``` 3. **使用纹理:** ```javascript gl.uniformTexture(program.uniforms.textureLocation, texture); ``` 4. **设置纹理坐标:** ```javascript texture.coords = uv; // uv为纹理坐标 ``` **局部马赛克效果:** 1. **设置马赛克中心:** ```javascript renderer.uniforms.center = [center.x, center.y]; ``` 2. **设置马赛克范围:** ```javascript const radiusPX = 100; renderer.uniforms.radiusX = radiusPX / canvasRef.value.width; renderer.uniforms.radiusY = radiusPX / canvasRef.value.height; ``` 3. **计算片元的索引:** ```javascript vec2 st = vUv * vec2(50, 27.7); ``` 4. **判断是否在马赛克范围内:** ```javascript 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) { // 在马赛克范围内,进行马赛克处理 } ``` **示例代码:** ```javascript // 初始化纹理 const texture = renderer.getContext('2d').createTexture(); texture.image = img; texture.minFilter = gl.LINEAR; // 设置纹理坐标 texture.coords = uv; // 设置马赛克中心 renderer.uniforms.center = [center.x, center.y]; // 设置马赛克范围 const radiusPX = 100; renderer.uniforms.radiusX = radiusPX / canvasRef.value.width; renderer.uniforms.radiusY = radiusPX / canvasRef.value.height; // 计算片元的索引 vec2 st = vUv * vec2(50, 27.7); // 判断是否在马赛克范围内 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) { // 在马赛克范围内,进行马赛克处理 } ``` **总结:** 纹理是图像处理中的重要基础概念,可以让我们使用图片呈现不同的视觉效果。通过了解纹理的基本知识和实现方法,我们可以通过设置纹理坐标、马赛克中心和范围等参数, achieve不同的马赛克效果。

正文

前言

接触过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实现简易的局部“马赛克”相似的内容:

WebGL实现简易的局部“马赛克”

在Canvas2D中通过调用`drawImage` API就能将图像绘制到画布上,在WebGL中我们也可以绘制图像,在绘制时我们需要用到WebGL中的纹理对象,在之前实现网格背景的视频中,我使用了一个叫做纹理坐标的配置,现在要完成纹理的加载我们也需要用到纹理坐标,并且我们可以通过对纹理坐标处理实现简...

如何使用webgl(three.js)实现煤矿隧道、井下人员定位、掘进面、纵采面可视化解决方案——第十九课(一)

three.js、webgl、3D煤矿隧道、三维井下人员定位、掘进面三维可视化、纵采面可视化、采集面可视化展示、设备检测、数字孪生、物联网3D、3d建筑、3d库房,bim管理系统

three.js实现相机碰撞,相机不穿墙壁、物体

大家好,本文实现了相机碰撞检测,使相机不穿墙壁、物体,并给出了思路和代码,感谢大家~ 关键词:数字孪生、three.js、Web3D、WebGL、相机碰撞、游戏相机 我正在承接Web3D数字孪生项目,具体介绍可看承接各种Web3D业务 目录实现原理参考资料 实现前: 移动第三人称相机时,相机可能会穿

WebGL:使用着色器进行几何造型

本文将介绍如何使用着色器来进行几何造型,说到几何图形大家一定都不陌生,比如说三角形、圆形,在WebGL中除了直接使用顶点绘制,我们还可以使用片元着色器进行造型,以下将介绍三角形的绘制,以及应用场景。

webgl centroid质心插值的一点理解

质心插值说的是什么 2023.10.04再次review这个细节点: https://www.opengl.org/pipeline/article/vol003_6/ https://github.com/WebGLSamples/WebGL2Samples/blob/master/samples

如何基于three.js(webgl)引擎架构,研发一套通过配置就能自动生成的3D机房系统

自动化3D机房、微模块、3D机房、3D数据中心、科技感数据中心、三维机房、3d建筑,3d消防,消防演习模拟,3d库房,3d档案室,3d密集架,webGL,threejs,3d机房,bim管理系统

three.js高性能渲染室外场景

大家好,本文在相关文章的基础上,使用three.js渲染了高性能的室外场景,在移动端也有较好的性能,并给出了代码,分析了关键点,感谢大家~ 关键词:three.js、Web3D、WebGL、室外场景、Instanced Draw、大场景、LOD、Frustum Cull、优化、开源 代码:Githu

第135篇:Three.js基础入门

好家伙,这东西太帅了,我要学会 先放张帅图(都是用three.js做出来的,这我学习动力直接拉满) 还有另外一个 Junni is... 帧数太高,录不了 开始学习 官方文档 1.Three.js是什么? Three.js是一款运行在浏览器中的 3D 引擎(基于WebGL的API的封装),你可以用它