webgl 图像处理2---图像像素处理

webgl 图像处理

webgl 不仅仅可以用来进行图形可视化, 它还能进行图像处理

图像处理2---图像传输

之前已经进行了点和 uv 数据的传输

webgl 进行图形处理的第二步: 传输图片到 GPU

下图为传输图片并进行相应渲染的结果

image-20210919224155536

对图像进行模糊处理, 并转换为数组输出

image-20210919224314334

处理过程详解

  1. 加载图片

    1. 由于加载图片是异步方法, 因此很多内容都需要写在加载图片的回调函数内
    2. 在回调函数中进行传输图片操作
  2. 传输图片到 GPU

    1. 之前传输数据的步骤
      1. 创建缓存区
      2. 绑定缓冲区
      3. 向缓存区中写入内容
      4. 绑定 shader 中的变量
      5. 开始传输数据
    2. 现在传输图像的步骤, 类似
      1. 创建材质 Texture ( 对应前面第 1 步 )
      2. y 轴反转, 这是由于浏览器 y 轴向下, 需要矫正
      3. 激活纹理单元 ( 简单理解为与纹理绑定的 内容, 一个纹理绑定一个纹理单元 )
      4. 绑定 texture ( 对应前面第 2 步)
      5. 配置图片信息, 两种, 一种是缩放相关参数, 用来将图片的宽高置为 2 的幂次方, 一种是图片内容 ( 对应前面第 3 步)
      6. 传输图片 ( 对应前面第 4,5 步)
  3. 查询当前像素的上下左右的颜色值并进行平均

    1. 当前节点的 uv 为 vUv, 是一个二维向量, 范围从 0-1
    2. 由于图片设置为 200 * 200, 因此可以将 vUv 转换为具体的像素点位置, floor(vUv * 200.0)/200.0
    3. 计算周边点的位置及像素值
      1. 例如该像素左边点位置为, floor(vUv * 200.0 + vec2(-1.0, 0.0)) / 200.0
      2. 像素值为, texture2D(u_Texture, floor(vUv * 200.0 + vec2(-1.0, 0.0)) / 200.0)
  4. 输出图像到数组中

    // 将图片数据加载到 pixels 数组中
    const pixels = new Uint8Array(200 * 200 *4);
    gl.readPixels(0, 0, 200, 200, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
    console.log(pixels);
    

    最后去得到一个 arrayBuffer 数组

下一阶段

当前阶段对图片进行像素颗粒的控制, 利用这个思路能实现大部分对图片的操作

下个阶段是输入一个数组, 在 GPU 中对数组进行计算, 最后得到相应的数值, 加速计算, 充分利用 GPU 并行计算的能力

代码实现

import Img from "./img/img1.jpg";

// 两种着色器
const VSHADER_SOURCE = `
  attribute vec4 a_Position;
  attribute vec2 uv;
  varying vec2 vUv;
  void main(){
    // 进行插值计算
    vUv = uv;
    gl_Position = a_Position;
  }
`;

const FSHADER_SOURCE = `
  // 片元着色器中一定要声明精度
  precision mediump float;
  varying vec2 vUv;
  uniform sampler2D u_Texture;

  // 求平均
  vec4 calculate(vec4 color, vec2 vUv){
    vec4 tempColor = color;
    if(vUv.x != 0.0 && vUv.y != 0.0){
      vec4 left = texture2D(u_Texture, floor(vUv * 200.0 + vec2(-1.0, 0.0)) / 200.0);
      vec4 right = texture2D(u_Texture, floor(vUv * 200.0 + vec2(1.0, 0.0)) / 200.0);
      vec4 top = texture2D(u_Texture, floor(vUv * 200.0 + vec2(0.0, 1.0)) / 200.0);
      vec4 bottom = texture2D(u_Texture, floor(vUv * 200.0 + vec2(0.0, -1.0)) / 200.0);
      // tempColor.rg = 1.0 * (left.rg + right.rg + top.rg + tempColor.rg + bottom.rg) / 5.0;
      tempColor = 1.0 * (left + right + top + tempColor + bottom) / 5.0;
    }

    return tempColor;
  }

  void main(){
    vec4 color = texture2D(u_Texture, vUv);

    color = calculate(color, vUv);

    gl_FragColor = color;
  }
`;

init();

function init() {
  const canvas = document.createElement("canvas");
  canvas.width = 200;
  canvas.height = 200;
  document.body.appendChild(canvas);

  // 获取 gl 环境
  const gl = canvas.getContext("webgl");
  if (!gl) {
    console.log("Fail to init content");
    return;
  }

  // webgl 程序
  const programe = gl.createProgram();

  // 初始化着色器
  initShader(gl, VSHADER_SOURCE, FSHADER_SOURCE, programe);

  // 发送数据
  sendData("a_Position", 2, [-1, 1, -1, -1, 1, -1, 1, 1], gl, programe);

  sendData("uv", 2, [0, 1, 0, 0, 1, 0, 1, 1], gl, programe);

  // 加载图片
  loadImage(gl, programe);

}

// 初始化着色器
function initShader(gl, VSHADER_SOURCE, FSHADER_SOURCE, programe) {
  // 创建 shader
  const vertexShader = gl.createShader(gl.VERTEX_SHADER);
  // 绑定资源
  gl.shaderSource(vertexShader, VSHADER_SOURCE);
  // 编译着色器
  gl.compileShader(vertexShader);
  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER, FSHADER_SOURCE);
  gl.shaderSource(fragmentShader, FSHADER_SOURCE);
  gl.compileShader(fragmentShader);

  // 常规流程
  gl.attachShader(programe, vertexShader);
  gl.attachShader(programe, fragmentShader);
  gl.linkProgram(programe);
  gl.useProgram(programe);
}

// 发送数据到 GPU
function sendData(name, size, arr, gl, programe) {
  // 获取地址空间
  const variate = gl.getAttribLocation(programe, name);
  if (variate < 0) {
    console.log(`Failed to get the location of ${name}`);
    return;
  }
  const variates = new Float32Array(arr);
  // 1. 创建缓存区
  const buffer = gl.createBuffer();
  if (!buffer) {
    console.log("Failed to create buffer");
  }
  // 2. 绑定缓存区
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  // 3. 向缓冲区中添加数据
  gl.bufferData(gl.ARRAY_BUFFER, variates, gl.STATIC_DRAW);
  // 4. 将缓冲区与 glsl 中变量绑定
  gl.vertexAttribPointer(variate, size, gl.FLOAT, false, 0, 0);
  // 5. 开始传输
  gl.enableVertexAttribArray(variate);
}

function loadImage(gl, programe){
  // 初始化 Image
  const image = new Image();
  // 通过 loader 加载图像路径
  image.src = Img;

  // 设置回调函数
  image.onload = ()=>{
    const texture = gl.createTexture();
    // y 轴反转
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    // 激活 0 号纹理单元
    gl.activeTexture(gl.TEXTURE0);
    // 绑定 texture
    gl.bindTexture(gl.TEXTURE_2D, texture);
    // 图像处理, 一定要有, 用来将图片宽高扩展到 2 的幂
    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);

    // 配置图片
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); // 配置纹理图像
    // 传输图片
    const u_Texture = gl.getUniformLocation(programe, "u_Texure");
    gl.uniform1i(u_Texture, 0);

    // 刷新颜色
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    // 清除
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 画图形
    gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);

    // 将图片数据加载到 pixels 数组中
    const pixels = new Uint8Array(200 * 200 *4);
    gl.readPixels(0, 0, 200, 200, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
    console.log(pixels);
  }

}
希望读者在看完后能提出意见, 点个赞, 鼓励一下, 我们一起进步. 加油 !!
原文地址:https://www.cnblogs.com/xiaxiangx/p/15313121.html