返回首页 WebGL 中文版

WebGL 基础

图像处理

2D 转换、旋转、伸缩、矩阵

3D

结构与组织

文本

WebGL 图像处理

在 WebGL 中图像处理是很简单的,多么简单?

为了在 WebGL 中绘制图像,我们需要使用纹理。类似于当渲染代替像素时,WebGL 会需要操作投影矩阵的坐标,WebGL 读取纹理时需要获取纹理坐标。纹理坐标范围是从 0.0 到 1.0。

因为我们仅需要绘制由两个三角形组成的矩形,我们需要告诉 WebGL 在矩阵中纹理对应的那个点。我们可以使用特殊的被称为多变变量,会将这些信息从顶点着色器传递到片段着色器。WebGL 将会插入这些值,这些值会在顶点着色器中,当对每个像素绘制时均会调用片段着色器。

我们需要在纹理坐标传递过程中添加更多的信息,然后将他们传递到片段着色器中。

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

然后,我们提供一个片段着色器来查找颜色纹理。

<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>

最后,我们需要加载一个图片,然后创建一个问题,将该图片传递到纹理里面。因为,是在浏览器里面显示,所以图片是异步加载,所以我们安置我们的代码来等待纹理的加载。一旦,加载完成就可以绘制。

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 渲染出来的图像。

下面我们对这个图片进行一些操作,来交换图片中的红色和蓝色。

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

现在红色和蓝色已经被交换了。效果如下:

假如我们想做一些图像处理,那么我们可以看一下其他像素。自从 WebGL 引用纹理的纹理坐标从 0.0 到 1.0 。然后,我们可以计算移动的多少个像素 onePixel = 1.0 / textureSize

这里有个片段着色器来平均纹理中每个像素的左侧和右侧的像素。

<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>

然后,我们需要通过 JavaScript 传递出纹理的大小。

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

比较上述两个图片

现在,我们知道如何让使用像素卷积内核做一些常见的图像处理。这里,我们会使用 3x3 的内核。卷积内核就是一个 3x3 的矩阵,矩阵中的每个条目代表有多少像素渲染。然后,我们将这个结果除以内核的权重或 1.0.这里是一个非常好的参考文章这里有另一篇文章显示出一些实际代码,它是使用 C++ 写的

在我们的例子中我们要在着色器中做这样工作,这里是一个新的片段着色器。

<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 中,我们需要提供一个卷积内核和它的权重。

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

我们在列表框内选择不同的内核。

我们希望通过这篇文章讲解,能够让你觉得使用 WebGL 做图像处理很简单。下面,我们将讲解如何在一个图像上应用更多的效果