webgl 影像處理2---影像畫素處理

隨遇丿而安 發表於 2021-09-19
WebGL

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);
  }

}