webgl 影像處理
webgl 不僅僅可以用來進行圖形視覺化, 它還能進行影像處理
影像處理2---影像傳輸
之前已經進行了點和 uv 資料的傳輸
webgl 進行圖形處理的第二步: 傳輸圖片到 GPU
下圖為傳輸圖片並進行相應渲染的結果
對影像進行模糊處理, 並轉換為陣列輸出
處理過程詳解
-
載入圖片
- 由於載入圖片是非同步方法, 因此很多內容都需要寫在載入圖片的回撥函式內
- 在回撥函式中進行傳輸圖片操作
-
傳輸圖片到 GPU
- 之前傳輸資料的步驟
- 建立快取區
- 繫結緩衝區
- 向快取區中寫入內容
- 繫結 shader 中的變數
- 開始傳輸資料
- 現在傳輸影像的步驟, 類似
- 建立材質 Texture ( 對應前面第 1 步 )
- y 軸反轉, 這是由於瀏覽器 y 軸向下, 需要矯正
- 啟用紋理單元 ( 簡單理解為與紋理繫結的 內容, 一個紋理繫結一個紋理單元 )
- 繫結 texture ( 對應前面第 2 步)
- 配置圖片資訊, 兩種, 一種是縮放相關引數, 用來將圖片的寬高置為 2 的冪次方, 一種是圖片內容 ( 對應前面第 3 步)
- 傳輸圖片 ( 對應前面第 4,5 步)
- 之前傳輸資料的步驟
-
查詢當前畫素的上下左右的顏色值並進行平均
- 當前節點的 uv 為 vUv, 是一個二維向量, 範圍從 0-1
- 由於圖片設定為 200 * 200, 因此可以將 vUv 轉換為具體的畫素點位置, floor(vUv * 200.0)/200.0
- 計算周邊點的位置及畫素值
- 例如該畫素左邊點位置為, floor(vUv * 200.0 + vec2(-1.0, 0.0)) / 200.0
- 畫素值為, texture2D(u_Texture, floor(vUv * 200.0 + vec2(-1.0, 0.0)) / 200.0)
-
輸出影像到陣列中
// 將圖片資料載入到 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);
}
}