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

2018-10-03 11:20 更新

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

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

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

一種比較靈活的方式是使用兩種或更多的紋理和渲染效果來(lái)交替渲染,每次應(yīng)用一個(gè)效果,然后反復(fù)應(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

要做到這一點(diǎn),就需要?jiǎng)?chuàng)建幀緩存區(qū)。在 WebGL 和 OpenGL 中,幀緩存區(qū)實(shí)際上是一個(gè)非常不正式的名稱(chēng)。 WebGL/OpenGL 中的幀緩存實(shí)際上僅僅是一些狀態(tài)的集合,而不是真正的緩存。但是,每當(dāng)一種紋理到達(dá)幀緩存,我們就會(huì)渲染出這種紋理。

首先讓我們把舊的紋理創(chuàng)建代碼寫(xiě)成一個(gè)函數(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);   

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

// 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)在,我們生成一些核的集合,然后存儲(chǔ)到列表里來(lái)應(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)用每一個(gè),然后交替渲染。

// 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 程序希望渲染到畫(huà)板而不是幀緩存中的紋理.

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

最后,在原始例子中,當(dāng)需要渲染的時(shí)候,我們會(huì)翻轉(zhuǎn) Y 坐標(biāo)。這是因?yàn)?WebGL 會(huì)以 0 來(lái)顯示面板。 0 表示是左側(cè)底部的坐標(biāo),這不同于 2D 圖像的頂部左側(cè)的坐標(biāo)。當(dāng)渲染為幀緩存時(shí)就不需要了。這是因?yàn)閹彺娌⒉粫?huì)顯示出來(lái)。其部分是頂部還是底部是無(wú)關(guān)緊要的。所有重要的就是像素 0,0 在幀緩存里就對(duì)應(yīng)著 0。為了解決這一問(wèn)題,我們可以通過(guò)是否在著色器中添加更多輸入信息的方法來(lái)設(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>

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

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

在這個(gè)簡(jiǎn)單的例子中,通過(guò)使用單個(gè) GLSL 程序可以實(shí)現(xiàn)多個(gè)效果。

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

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

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)