在 WebGL 中圖像處理是很簡(jiǎn)單的,多么簡(jiǎn)單?
為了在 WebGL 中繪制圖像,我們需要使用紋理。類似于當(dāng)渲染代替像素時(shí),WebGL 會(huì)需要操作投影矩陣的坐標(biāo),WebGL 讀取紋理時(shí)需要獲取紋理坐標(biāo)。紋理坐標(biāo)范圍是從 0.0 到 1.0。
因?yàn)槲覀儍H需要繪制由兩個(gè)三角形組成的矩形,我們需要告訴 WebGL 在矩陣中紋理對(duì)應(yīng)的那個(gè)點(diǎn)。我們可以使用特殊的被稱為多變變量,會(huì)將這些信息從頂點(diǎn)著色器傳遞到片段著色器。WebGL 將會(huì)插入這些值,這些值會(huì)在頂點(diǎn)著色器中,當(dāng)對(duì)每個(gè)像素繪制時(shí)均會(huì)調(diào)用片段著色器。
我們需要在紋理坐標(biāo)傳遞過(guò)程中添加更多的信息,然后將他們傳遞到片段著色器中。
attribute vec2 a_texCoord;
...
varying vec2 v_texCoord;
void main() {
...
// pass the texCoord to the fragment shader
// The GPU will interpolate this value between points
v_texCoord = a_texCoord;
}
然后,我們提供一個(gè)片段著色器來(lái)查找顏色紋理。
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
// Look up a color from the texture.
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
最后,我們需要加載一個(gè)圖片,然后創(chuàng)建一個(gè)紋理,將該圖片傳遞到紋理里面。因?yàn)椋窃跒g覽器里面顯示,所以圖片是異步加載,所以我們安置我們的代碼來(lái)等待紋理的加載。一旦,加載完成就可以繪制。
function main() {
var image = new Image();
image.src = "http://someimage/on/our/server"; // MUST BE SAME DOMAIN!!!
image.onload = function() {
render(image);
}
}
function render(image) {
...
// all the code we had before.
...
// look up where the texture coordinates need to go.
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
// provide texture coordinates for the rectangle.
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
// Create a texture.
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
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);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
...
}
如下是 WebGL 渲染出來(lái)的圖像。
下面我們對(duì)這個(gè)圖片進(jìn)行一些操作,來(lái)交換圖片中的紅色和藍(lán)色。
...
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
...
現(xiàn)在紅色和藍(lán)色已經(jīng)被交換了。效果如下:
假如我們想做一些圖像處理,那么我們可以看一下其他像素。從 WebGL 引用紋理的紋理坐標(biāo)從 0.0 到 1.0 。我們可以計(jì)算移動(dòng)的多少個(gè)像素 onePixel = 1.0 / textureSize
。
這里有個(gè)片段著色器來(lái)平均紋理中每個(gè)像素的左側(cè)和右側(cè)的像素。
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
// compute 1 pixel in texture coordinates.
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
// average the left, middle, and right pixels.
gl_FragColor = (
texture2D(u_image, v_texCoord) +
texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
}
</script>
然后,我們需要通過(guò) JavaScript 傳遞出紋理的大小。
...
var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
...
// set the size of the image
gl.uniform2f(textureSizeLocation, image.width, image.height);
...
比較上述兩個(gè)圖片
現(xiàn)在,我們知道如何讓使用像素卷積內(nèi)核做一些常見的圖像處理。這里,我們會(huì)使用 3x3 的內(nèi)核。卷積內(nèi)核就是一個(gè) 3x3 的矩陣,矩陣中的每個(gè)條目代表有多少像素渲染。然后,我們將這個(gè)結(jié)果除以內(nèi)核的權(quán)重或 1.0.這里是一個(gè)非常好的參考文章。這里有另一篇文章顯示出一些實(shí)際代碼,它是使用 C++ 寫的。
在我們的例子中我們要在著色器中做這樣工作,這里是一個(gè)新的片段著色器。
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];
uniform float u_kernelWeight;
// the texCoords passed in from the vertex shader.
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] ;
// Divide the sum by the weight but just use rgb
// we'll set alpha to 1.0
gl_FragColor = vec4((colorSum / u_kernelWeight).rgb, 1.0);
}
</script>
在 JavaScript 中,我們需要提供一個(gè)卷積內(nèi)核和它的權(quán)重。
function computeKernelWeight(kernel) {
var weight = kernel.reduce(function(prev, curr) {
return prev + curr;
});
return weight <= 0 ? 1 : weight;
}
...
var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
var kernelWeightLocation = gl.getUniformLocation(program, "u_kernelWeight");
...
var edgeDetectKernel = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
];
gl.uniform1fv(kernelLocation, edgeDetectKernel);
gl.uniform1f(kernelWeightLocation, computeKernelWeight(edgeDetectKernel));
...
我們?cè)诹斜砜騼?nèi)選擇不同的內(nèi)核。
我們希望通過(guò)這篇文章講解,能夠讓你覺(jué)得使用 WebGL 做圖像處理很簡(jiǎn)單。下面,我們將講解如何在一個(gè)圖像上應(yīng)用更多的效果。
更多建議: