# 进一步处理图像
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);
}