# 进一步处理图像

GLSL

  • createFramebuffer bindFramebuffer framebufferTexture2D

图像处理的下一个问题是如何同时施加多种效果?

当然,你可以试着在运行时创建着色器,根据用户从交互界面选择的一些效果,创建一个可以全部实现的着色器。

尽管有人用过在运行时创建渲染效果,但是大部分情况下是不适合的。

一个更灵活的方式是使用2个或以上的纹理,然后交替渲染它们, 像乒乓球一样每次渲染一种效果,传给另一个渲染下一个效果,如下所示。

原始图像    -> [模糊]     -> 纹理 1
纹理 1      -> [锐化]     -> 纹理 2
纹理 2      -> [边缘检测] -> 纹理 1
纹理 1      -> [模糊]     -> 纹理 2
纹理 2      -> [平滑]     -> 画布

这个操作需要使用帧缓冲来实现。在WebGL和OpenGL中,帧缓冲是一个事实上是一个糟糕的名字。 WebGL/OpenGL 中的帧缓冲只是一系列状态(一列附加物)不是任何形式的缓冲。 但是当我们给帧缓冲绑定一个纹理后, 可以将渲染结果写入那个纹理。

首先让我们把以前创建纹理的代码写到一个方法里

export function createAndSetupTexture(gl) {
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // 设置材质,这样我们可以对任意大小的图像进行像素操作
  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);

  return texture;
}
// 创建一个纹理并写入图像
const originalImageTexture = createAndSetupTexture(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

现在让我们用这个方法生成两个纹理并绑定到两个帧缓冲。

// 创建两个纹理绑定到帧缓冲
const textures = [];
const framebuffers = [];
for (let i = 0; i < 2; ++i) {
  const texture = createAndSetupTexture(gl);
  textures.push(texture);

  // 设置纹理大小和图像大小一致
  gl.texImage2D(
      gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0,
      gl.RGBA, gl.UNSIGNED_BYTE, null);

  // 创建一个帧缓冲
  const fbo = gl.createFramebuffer();
  framebuffers.push(fbo);
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

  // 绑定纹理到帧缓冲
  gl.framebufferTexture2D(
      gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
}

现在让我们做一些卷积核并按使用顺序存入列表中

const kernels = {
  normal: [
    0, 0, 0, //
    0, 1, 0,
    0, 0, 0
  ],
  gaussianBlur: [
    0.045, 0.122, 0.045, //
    0.122, 0.332, 0.122,
    0.045, 0.122, 0.045
  ],
  unsharpen: [
    -1, -1, -1, //
    -1,  9, -1,
    -1, -1, -1
  ],
  emboss: [
      -2, -1,  0, //
      -1,  1,  1,
      0,  1,  2
  ]
};


// 将要使用的效果列表
const effectsToApply = [
  "gaussianBlur",
  "emboss",
  "gaussianBlur",
  "unsharpen"
];

最后让我们使用所有渲染效果,像乒乓一样来回渲染

// 从原始图像开始
gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);

// 在渲染效果时不翻转y轴
gl.uniform1f(flipYLocation, 1);

// 循环施加每一种渲染效果
for (let i = 0; i < effectsToApply.length; ++i) {
  // 使用两个帧缓冲中的一个
  setFramebuffer(framebuffers[i % 2], image.width, image.height);

  drawWithKernel(effectsToApply[i]);

  // 下次绘制时使用刚才的渲染结果
  gl.bindTexture(gl.TEXTURE_2D, textures[i % 2]);
}

// 最后将结果绘制到画布
gl.uniform1f(flipYLocation, -1);  // 需要绕y轴翻转
setFramebuffer(null, canvas.width, canvas.height);
drawWithKernel("normal");

function setFramebuffer(fbo, width, height) {
  // 设定当前使用帧缓冲
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

  // 告诉着色器分辨率是多少
  gl.uniform2f(resolutionLocation, width, height);

  // 告诉WebGL帧缓冲需要的视图大小
  gl.viewport(0, 0, width, height);
}

function drawWithKernel(name) {
  // 设置卷积核
  gl.uniform1fv(kernelLocation, kernels[name]);

  // 画出矩形
  gl.drawArrays(gl.TRIANGLES, 0, 6);
}
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
uniform float u_flipY;
varying vec2 v_texCoord;

void main() {
   vec2 zeroToOne = a_position / u_resolution;
   vec2 zeroToTwo = zeroToOne * 2.0;
   vec2 clipSpace = zeroToTwo - 1.0;
   gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
   v_texCoord = a_texCoord;
}
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];
uniform float u_kernelWeight;
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] ;
   gl_FragColor = vec4((colorSum / u_kernelWeight).rgb, 1);
}

# 渲染结果