這篇文章是 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 更容易理解。
更多建議: