鍍金池/ 教程/ HTML/ WebGL 圖像處理(續(xù))
WebGL 文本 HTML
WebGL 文本 Canvas 2D
WebGL 2D 圖像旋轉(zhuǎn)
WebGL 圖像處理(續(xù))
WebGL 2D 矩陣
WebGL 繪制多個東西
WebGL 圖像處理
WebGL 2D 圖像轉(zhuǎn)換
WebGL 3D 透視
WebGL 是如何工作的
WebGL 文本 紋理
WebGL 2D 圖像伸縮
WebGL 場景圖
WebGL 3D 攝像機
WebGL 文本 使用字符紋理
WebGL 正交 3D
WebGL 基本原理
WebGL - 更少的代碼,更多的樂趣
WebGL 著色器和 GLSL

WebGL 圖像處理(續(xù))

這篇文章是 WebGL 圖像處理內(nèi)容擴展。

下一個關(guān)于圖像處理的顯著問題就是如何應(yīng)用多重效果?讀者當然可以嘗試著寫一下著色器。生成一個 UI 來讓用戶使用不同的著色器選擇他們希望的效果。這通常是不太可能的,因為這個技術(shù)通常需要實時的渲染效果。

一種比較靈活的方式是使用兩種或更多的紋理和渲染效果來交替渲染,每次應(yīng)用一個效果,然后反復應(yīng)用。

Original Image -> [Blur]-> Texture 1
Texture 1  -> [Sharpen] -> Texture 2
Texture 2  -> [Edge Detect] -> Texture 1
Texture 1  -> [Blur]-> Texture 2
Texture 2  -> [Normal]  -> Canvas

要做到這一點,就需要創(chuàng)建幀緩存區(qū)。在 WebGL 和 OpenGL 中,幀緩存區(qū)實際上是一個非常不正式的名稱。 WebGL/OpenGL 中的幀緩存實際上僅僅是一些狀態(tài)的集合,而不是真正的緩存。但是,每當一種紋理到達幀緩存,我們就會渲染出這種紋理。

首先讓我們把舊的紋理創(chuàng)建代碼寫成一個函數(shù)。

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

// Set up texture so we can render any size image and so we are
// working with pixels.
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;
  }

  // Create a texture and put the image in it.
  var originalImageTexture = createAndSetupTexture(gl);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);   

然后,我們使用這兩個函數(shù)來生成兩種問題,并且附在兩個幀緩存中。

// create 2 textures and attach them to framebuffers.
  var textures = [];
  var framebuffers = [];
  for (var ii = 0; ii < 2; ++ii) {
var texture = createAndSetupTexture(gl);
textures.push(texture);

// make the texture the same size as the image
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);

// Create a framebuffer
var fbo = gl.createFramebuffer();
framebuffers.push(fbo);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

// Attach a texture to it.
gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
  }   

現(xiàn)在,我們生成一些核的集合,然后存儲到列表里來應(yīng)用。

  // Define several convolution kernels
  var 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
]
  };

  // List of effects to apply.
  var effectsToApply = [
"gaussianBlur",
"emboss",
"gaussianBlur",
"unsharpen"
  ];   

最后,我們應(yīng)用每一個,然后交替渲染。

// start with the original image
  gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);

  // don't y flip images while drawing to the textures
  gl.uniform1f(flipYLocation, 1);

  // loop through each effect we want to apply.
  for (var ii = 0; ii < effectsToApply.length; ++ii) {
// Setup to draw into one of the framebuffers.
setFramebuffer(framebuffers[ii % 2], image.width, image.height);

drawWithKernel(effectsToApply[ii]);

// for the next draw, use the texture we just rendered to.
gl.bindTexture(gl.TEXTURE_2D, textures[ii % 2]);
  }

  // finally draw the result to the canvas.
  gl.uniform1f(flipYLocation, -1);  // need to y flip for canvas
  setFramebuffer(null, canvas.width, canvas.height);
  drawWithKernel("normal");

  function setFramebuffer(fbo, width, height) {
// make this the framebuffer we are rendering to.
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

// Tell the shader the resolution of the framebuffer.
gl.uniform2f(resolutionLocation, width, height);

// Tell webgl the viewport setting needed for framebuffer.
gl.viewport(0, 0, width, height);
  }

  function drawWithKernel(name) {
// set the kernel
gl.uniform1fv(kernelLocation, kernels[name]);

// Draw the rectangle.
gl.drawArrays(gl.TRIANGLES, 0, 6);
  }   

下面是更靈活的 UI 的可交互版本。勾選相應(yīng)的效果即可檢查效果。

以空值調(diào)用 gl.bindFramebuffer 即可告訴 WebGL 程序希望渲染到畫板而不是幀緩存中的紋理.

WebGL 不得不將投影矩陣轉(zhuǎn)換為像素。這是基于 gl.viewport 的設(shè)置。當我們初始化 WebGL 的時候, gl.viewport 的設(shè)置默認為畫板的尺寸。因為,我們會將幀緩存渲染為不同的尺寸,所以畫板需要設(shè)置合適的視圖。

最后,在原始例子中,當需要渲染的時候,我們會翻轉(zhuǎn) Y 坐標。這是因為 WebGL 會以 0 來顯示面板。 0 表示是左側(cè)底部的坐標,這不同于 2D 圖像的頂部左側(cè)的坐標。當渲染為幀緩存時就不需要了。這是因為幀緩存并不會顯示出來。其部分是頂部還是底部是無關(guān)緊要的。所有重要的就是像素 0,0 在幀緩存里就對應(yīng)著 0。為了解決這一問題,我們可以通過是否在著色器中添加更多輸入信息的方法來設(shè)置是否快讀交替。

<script id="2d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_flipY;
...

void main() {
   ...
   gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
   ...
}
</script>

當我們渲染的時候,就可以設(shè)置它。

  var flipYLocation = gl.getUniformLocation(program, "u_flipY");
  ...
  // don't flip
  gl.uniform1f(flipYLocation, 1);
  ...
  // flip
  gl.uniform1f(flipYLocation, -1);

在這個簡單的例子中,通過使用單個 GLSL 程序可以實現(xiàn)多個效果。

如果你想做完整的圖像處理你可能需要許多 GLSL 程序。一個程序?qū)崿F(xiàn)色相、飽和度和亮度調(diào)整。另一個實現(xiàn)亮度和對比度。一個實現(xiàn)反相,另一個用于調(diào)整水平。你可能需要更改代碼以更新 GLSL 程序和更新特定程序的參數(shù)。我本來考慮寫出這個例子,但這是一個練習,所以最好留給讀者自己實現(xiàn),因為多個 GLSL 項目中每一種方法都有自己的參數(shù),可能意味著需要一些重大重構(gòu),這很可能導致成為意大利面條似的大混亂。

我希望從這里和前面的示例中可以看出 WebGL 似乎更平易近人,我希望從 2D 方面入手,以有助于使 WebGL 更容易理解。