WebGL 圖像處理

2023-09-19 09:53 更新

WebGL 圖像處理

在 WebGL 中圖像處理是很簡(jiǎn)單的,多么簡(jiǎn)單?

為了在 WebGL 中繪制圖像,我們需要使用紋理。類似于當(dāng)渲染代替像素時(shí),WebGL 會(huì)需要操作投影矩陣的坐標(biāo),WebGL 讀取紋理時(shí)需要獲取紋理坐標(biāo)。紋理坐標(biāo)范圍是從 0.0 到 1.0。

因?yàn)槲覀儍H需要繪制由兩個(gè)三角形組成的矩形,我們需要告訴 WebGL 在矩陣中紋理對(duì)應(yīng)的那個(gè)點(diǎn)。我們可以使用特殊的被稱為多變變量,會(huì)將這些信息從頂點(diǎn)著色器傳遞到片段著色器。WebGL 將會(huì)插入這些值,這些值會(huì)在頂點(diǎn)著色器中,當(dāng)對(duì)每個(gè)像素繪制時(shí)均會(huì)調(diào)用片段著色器。

我們需要在紋理坐標(biāo)傳遞過(guò)程中添加更多的信息,然后將他們傳遞到片段著色器中。

attribute vec2 a_texCoord;
...
varying vec2 v_texCoord;

void main() {
   ...
   // pass the texCoord to the fragment shader
   // The GPU will interpolate this value between points
   v_texCoord = a_texCoord;
}

然后,我們提供一個(gè)片段著色器來(lái)查找顏色紋理。

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   // Look up a color from the texture.
   gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>

最后,我們需要加載一個(gè)圖片,然后創(chuàng)建一個(gè)紋理,將該圖片傳遞到紋理里面。因?yàn)椋窃跒g覽器里面顯示,所以圖片是異步加載,所以我們安置我們的代碼來(lái)等待紋理的加載。一旦,加載完成就可以繪制。

function main() {
  var image = new Image();
  image.src = "http://someimage/on/our/server";  // MUST BE SAME DOMAIN!!!
  image.onload = function() {
render(image);
  }
}

function render(image) {
  ...
  // all the code we had before.
  ...
  // look up where the texture coordinates need to go.
  var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");

  // provide texture coordinates for the rectangle.
  var texCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
  0.0,  0.0,
  1.0,  0.0,
  0.0,  1.0,
  0.0,  1.0,
  1.0,  0.0,
  1.0,  1.0]), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(texCoordLocation);
  gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

  // Create a texture.
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set the parameters so we can render any size 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.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  // Upload the image into the texture.
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  ...
}

如下是 WebGL 渲染出來(lái)的圖像。

下面我們對(duì)這個(gè)圖片進(jìn)行一些操作,來(lái)交換圖片中的紅色和藍(lán)色。

...
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
...

現(xiàn)在紅色和藍(lán)色已經(jīng)被交換了。效果如下:

假如我們想做一些圖像處理,那么我們可以看一下其他像素。從 WebGL 引用紋理的紋理坐標(biāo)從 0.0 到 1.0 。我們可以計(jì)算移動(dòng)的多少個(gè)像素 onePixel = 1.0 / textureSize。

這里有個(gè)片段著色器來(lái)平均紋理中每個(gè)像素的左側(cè)和右側(cè)的像素。

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   // compute 1 pixel in texture coordinates.
   vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;

   // average the left, middle, and right pixels.
   gl_FragColor = (
   texture2D(u_image, v_texCoord) +
   texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
   texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
}
</script>

然后,我們需要通過(guò) JavaScript 傳遞出紋理的大小。

...
var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
...
// set the size of the image
gl.uniform2f(textureSizeLocation, image.width, image.height);
...

比較上述兩個(gè)圖片

現(xiàn)在,我們知道如何讓使用像素卷積內(nèi)核做一些常見的圖像處理。這里,我們會(huì)使用 3x3 的內(nèi)核。卷積內(nèi)核就是一個(gè) 3x3 的矩陣,矩陣中的每個(gè)條目代表有多少像素渲染。然后,我們將這個(gè)結(jié)果除以內(nèi)核的權(quán)重或 1.0.這里是一個(gè)非常好的參考文章。這里有另一篇文章顯示出一些實(shí)際代碼,它是使用 C++ 寫的

在我們的例子中我們要在著色器中做這樣工作,這里是一個(gè)新的片段著色器。

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];
uniform float u_kernelWeight;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
   vec4 colorSum =
 texture2D(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
 texture2D(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
 texture2D(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
 texture2D(u_image, v_texCoord + onePixel * vec2(-1,  0)) * u_kernel[3] +
 texture2D(u_image, v_texCoord + onePixel * vec2( 0,  0)) * u_kernel[4] +
 texture2D(u_image, v_texCoord + onePixel * vec2( 1,  0)) * u_kernel[5] +
 texture2D(u_image, v_texCoord + onePixel * vec2(-1,  1)) * u_kernel[6] +
 texture2D(u_image, v_texCoord + onePixel * vec2( 0,  1)) * u_kernel[7] +
 texture2D(u_image, v_texCoord + onePixel * vec2( 1,  1)) * u_kernel[8] ;

   // Divide the sum by the weight but just use rgb
   // we'll set alpha to 1.0
   gl_FragColor = vec4((colorSum / u_kernelWeight).rgb, 1.0);
}
</script>

在 JavaScript 中,我們需要提供一個(gè)卷積內(nèi)核和它的權(quán)重。

 function computeKernelWeight(kernel) {
   var weight = kernel.reduce(function(prev, curr) {
   return prev + curr;
   });
   return weight <= 0 ? 1 : weight;
 }

 ...
 var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
 var kernelWeightLocation = gl.getUniformLocation(program, "u_kernelWeight");
 ...
 var edgeDetectKernel = [
 -1, -1, -1,
 -1,  8, -1,
 -1, -1, -1
 ];
 gl.uniform1fv(kernelLocation, edgeDetectKernel);
 gl.uniform1f(kernelWeightLocation, computeKernelWeight(edgeDetectKernel));
 ...

我們?cè)诹斜砜騼?nèi)選擇不同的內(nèi)核。

我們希望通過(guò)這篇文章講解,能夠讓你覺(jué)得使用 WebGL 做圖像處理很簡(jiǎn)單。下面,我們將講解如何在一個(gè)圖像上應(yīng)用更多的效果。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)