WebGL 理论基础 - 图像处理 下

本文来源:https://zhuanlan.zhihu.com/p/52611681

-------------------------------------------------------------

此文上接WebGL 图像处理,如果还没有读过建议你从那里开始

图像处理的下一个问题是如何同时施加多种效果?

当然,你可以试着在运行时创建着色器,根据用户从交互界面选择的一些效果,创建一个可以全部实现的着色器。尽管有人用过 在运行时创建渲染效果,但是大部分情况下是不适合的。

一个更灵活的方式是使用2个或以上的纹理,然后交替渲染它们, 像乒乓球一样每次渲染一种效果,传给另一个渲染下一个效果,如下所示。

原始图像 -> 【模糊】 -> 纹理 1

纹理 1 -> 【锐化】 -> 纹理 2

纹理 2 -> 【边缘检测】 -> 纹理 1

纹理 1 -> 【模糊】 -> 纹理 2

纹理 2 -> 【平滑】 -> 画布

这个操作需要使用帧缓冲来实现。在 WebGL 和 OpenG L中,帧缓冲是一个事实上是一个糟糕的名字。 WebGL / OpenGL 中的帧缓冲只是一系列状态(一列附加物)不是任何形式的缓冲。 但是当我们给帧缓冲绑定一个纹理后, 可以将渲染结果写入那个纹理。

首先让我们把以前创建纹理的代码写到一个方法里

function createAndSetupTexture(gl) {
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // 设置材质,这样我们可以对任意大小的图像进行像素操作
    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.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

    return texture;
}

// 创建一个纹理并写入图像
var originalImageTexture = createAndSetupTexture(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

现在让我们用这个方法生成两个纹理并绑定到两个帧缓冲。

// 创建两个纹理绑定到帧缓冲
var textures = [];
var framebuffers = [];
for (var ii = 0; ii < 2; ++ii) {
    var texture = createAndSetupTexture(gl);
    textures.push(texture);

    // 设置纹理大小和图像大小一致
    gl.texImage2D(
        gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0,
        gl.RGBA, gl.UNSIGNED_BYTE, null);

    // 创建一个帧缓冲
    var fbo = gl.createFramebuffer();
    framebuffers.push(fbo);
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

    // 绑定纹理到帧缓冲
    gl.framebufferTexture2D(
        gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
}

现在让我们做一些卷积核并按使用顺序存入列表中

// 定义一些卷积核
var kernels = {
    normal: [
      0, 0, 0,
      0, 1, 0,
      0, 0, 0
    ],
    gaussianBlur: [
      0.045, 0.122, 0.045,
      0.122, 0.332, 0.122,
      0.045, 0.122, 0.045
    ],
    unsharpen: [
      -1, -1, -1,
      -1,  9, -1,
      -1, -1, -1
    ],
    emboss: [
       -2, -1,  0,
       -1,  1,  1,
        0,  1,  2
    ]
};

// 将要使用的效果列表
var effectsToApply = [
    "gaussianBlur",
    "emboss",
    "gaussianBlur",
    "unsharpen"
];

最后让我们使用所有渲染效果,像乒乓一样来回渲染

// 从原始图像开始
gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);

// 在渲染效果时不翻转y轴
gl.uniform1f(flipYLocation, 1);

// 循环施加每一种渲染效果
for (var ii = 0; ii < effectsToApply.length; ++ii) {
    // 使用两个帧缓冲中的一个
    setFramebuffer(framebuffers[ii % 2], image.width, image.height);

    drawWithKernel(effectsToApply[ii]);

    // 下次绘制时使用刚才的渲染结果
    gl.bindTexture(gl.TEXTURE_2D, textures[ii % 2]);
}

// 最后将结果绘制到画布
gl.uniform1f(flipYLocation, -1);  // 需要绕y轴翻转
setFramebuffer(null, canvas.width, canvas.height);
drawWithKernel("normal");

function setFramebuffer(fbo, width, height) {
    // 设定当前使用帧缓冲
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

    // 告诉着色器分辨率是多少
    gl.uniform2f(resolutionLocation, width, height);

    // 告诉WebGL帧缓冲需要的视图大小
    gl.viewport(0, 0, width, height);
}

function drawWithKernel(name) {
    // 设置卷积核
    gl.uniform1fv(kernelLocation, kernels[name]);

    // 画出矩形
    gl.drawArrays(gl.TRIANGLES, 0, 6);
}

下面是一个可交互示例,用了稍微灵活一点的用户交互。勾选表示开启对应效果, 拖拽改变渲染顺序。

CodePen 地址(https://codepen.io/princessgod/pen/JwKZxE?&editable=true&editors=101)

有些东西需要回顾一下。

调用 gl.bindFramebuffer 设置为 null 是告诉 WebGL 你想在画布上绘制,而不是在帧缓冲上。

WebGL 需要从裁剪空间对应到屏幕像素,设置 gl.viewport 就是为了实现这个。因为我们的帧缓冲的大小和画布的大小不同,所以我们需要给帧缓冲设置一个合适的视图大小让它渲染到对应的纹理上,最后再渲染到画布上。

原例中,我们在渲染时绕 y 轴翻转是因为WebGL的 0, 0 点在左下角而不是常见二维屏幕坐标的左上角。而在帧缓冲中绘制的时候不需要翻转,因为帧缓冲不用显示,谁上谁下无所谓,最重要的是我们计算中的 0, 0 也对应帧缓冲中的 0, 0 像素。为了解决这个问题,通过在着色器中添加一个输入来决定是否翻转。

<script id="2d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_flipY;
...

void main() {
   ...

   gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);

   ...
}

然后在渲染的时候可以这样设置

...

var flipYLocation = gl.getUniformLocation(program, "u_flipY");

...

// 不翻转
gl.uniform1f(flipYLocation, 1);

...

// 翻转
gl.uniform1f(flipYLocation, -1);

为了让这个例子简单化,我只用了一个 GLSL 实现了多种渲染效果。 如果专做图像处理可能需要多个GLSL程序,一个调节色彩,饱和度和明度, 一个调节亮度和对比度,一个做反色,一个做色彩平衡,等等。 你需要用代码更换 GLSL,并更新程序对应的参数。我想过写一个类似的例子, 但最好留给读者自己实现,因为多个 GLSL 程序和参数需要良好的重构, 不然代码会一团糟,所以它是一个很好的练习机会。

WebGL 理论基础 - 基础概念

WebGL 理论基础 - 工作原理

WebGL 理论基础 - 着色器和 GLSL

WebGL 理论基础 - 图像处理 上

技术交流qq群:850038125

微信群:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
webgl-fingerprint-defende.crx 是一个浏览器扩展文件,它的作用是提供WebGL指纹防御功能。WebGL是一种用于在网页浏览器中渲染3D图形的技术,而指纹则是通过收集浏览器和计算机的信息来识别用户身份的一种方法。 通常,浏览器会从操作系统和硬件中收集一些信息来创建一个独特的指纹识别码。这个识别码可以随着浏览器的使用而变化,因此可以用作用户识别。然而,一些网站可能会滥用这种技术来跟踪用户的在线行为,侵犯用户隐私。 webgl-fingerprint-defende.crx 文件可以帮助用户保护自己的隐私,防止被WebGL指纹识别出来。它通过修改浏览器的WebGL指纹数据,使之变得随机或无法识别。这样,即使网站尝试使用WebGL指纹进行用户跟踪,也无法准确识别用户的真实身份。 使用 webgl-fingerprint-defende.crx 文件可以有效地防止被WebGL指纹追踪,保护用户的个人隐私。它的安装和使用也非常简单,只需将文件添加到浏览器的扩展管理页面即可。然后,在用户浏览网页时,该扩展将自动激活并对WebGL指纹进行保护。 需要注意的是,虽然这个扩展可以有效防止WebGL指纹追踪,但在使用时仍需注意个人隐私的其他方面。同时,由于浏览器和WebGL技术的不断更新和演变,扩展的效果可能会有所变化。因此,保持扩展的更新和关注相关的隐私保护措施是很重要的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值