在webgl的使用過程中,我們通常會想對texture進行多級處理並對其貼在表面顯示
如對較精准的邊緣檢測,要先后使用灰度shader、模糊shader、邊緣shader來進行處理,而每次的處理對象則是上一次處理后的texture,這就要對處理后的結果進行覆蓋保存。
這是我在做Polyer使用到的:http://zhiyishou.github.io/Polyer
在眾多webgl庫中,直接有選項rederToTarget來實現將shader處理后的texture渲染並覆蓋原texture,其是怎么完成這個步驟的呢?
這就要引出本文的主角——FrameBuffer
FrameBuffer是什么
FBO(Frame Buffer Object)是被推薦用於將數據渲染到紋理對象的擴展。
FrameBuffer就像是一個webgl顯示容器一樣,平時我們使用gl.drawArrays或者gl.drawElements都是將對象繪制在了默認的窗口中,而當我們指定一個FrameBuffer為當前窗口時,則用這兩個方法去繪制,則會將對象繪制於指定的FrameBuffer中。
FrameBuffer的使用
internalformat, int x, int y, sizei width,
sizei height, int border);
target: TEXTURE_2D, TEXTURE_
FBO的創建:
//創建一個Framebuffer var fb = gl.createFramebuffer(); //將fb綁定為目前的窗口 gl.bindFramebuffer(gl.FRAMEBUFFER,fb);
這樣,我們則創建了一個新的可以繪制的buffer了,且其並不會被顯示出來
但是,這樣就可以了嗎?我們想到的是將經過shader渲染后的texture渲染出來並交給下一個shader,這時則引入方法framebufferTexture2D
Reference from《OpenGL ES Reference Pages about FramebufferTexture2D》:
To render directly into a texture image, a specified image from a texture object can be attached as one of the logical buffers of the currently bound framebuffer object by calling the command
為了直接渲染至紋理圖片中,一個紋理對象中指定的圖片可用下面的方法綁定在當前使用的FBO上一個邏輯緩存中
void FramebufferTexture2D( enum target, enum attachment, enum textarget, uint texture, int level );
target:
• FRAMEBUFFER
attachment:
• If attachment is COLOR_ATTACHMENT0, then image must have a colorrenderable internal format.(色彩)
• If attachment is DEPTH_ATTACHMENT, then image must have a depthrenderable internal format.(深度)
• If attachment is STENCIL_ATTACHMENT, then image must have a stencilrenderable internal format.(模板)
textarget:
• TEXTURE_2D (two-dimensional texture)
• TEXTURE_CUBE_MAP_POSITIVE_X (three-dimensional +x texture)
• TEXTURE_CUBE_MAP_POSITIVE_Y (three-dimensional +y texture)
• TEXTURE_CUBE_MAP_POSITIVE_Z (three-dimensional +z texture)
• TEXTURE_CUBE_MAP_NEGATIVE_X (three-dimensional -x texture)
• TEXTURE_CUBE_MAP_NEGATIVE_Y (three-dimensional -y texture)
• TEXTURE_CUBE_MAP_NEGATIVE_Z (three-dimensional -z texture)
texture:
texture object
level:
specifies the mipmap level of the texture image to be attached to the framebuffer and must be 0.
我們使用這個方法來進行綁定(本文只介紹色彩的綁定,嘗試和模板類似,但是有不同之處,不在此討論)
//創建一個紋理對象 var texture = gl.createTexture(); //使用如下的設置來創建texture,這樣對texture的設置可以使我們對任何尺寸的圖片進行處理 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);
var fb = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER,fb); //使用該方法將texture的顏色值與FBO進行綁定 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
綁定后,當我們執行gl.drawArrays或gl.drawElements方法時,則會將直接渲染至目前綁定的FBO上,而FBO又與texture的色彩進行了綁定,所以繪制時則也將色彩渲染至了texture中
這樣,我們則可用兩個FBO來進行隊列加工:
OriginalImage --> texture1
texture1 --> gray --> texture2
texture2 --> blur --> texture1
texture1 --> edge --> texture2
下面是具體實現過程
var FBOs = [], textures = []; for(var i = 0; i < 2; i++){ 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); var fb = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER,fb); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); //store corresponding texture and fb textures.push(texture); FBOs.push(fb); } gl.bindTexture(gl.TEXTURE_2D, originalImageTexture); for(var i = 0; i < 3; i++){ switch(i){case 0: //set gray shader to current shader program //handle arguments to vs shader and fs shader break; case 1: //set blur shader to current shader program //handle arguments to vs shader and fs shader break; case 2: //set edge shader to current shader program //handle arguments to vs shader and fs shader break; } gl.bindFramebuffer(gl.FRAMEBUFFER, FBOs[i%2]); //set the viewport fits the images size gl.viewport(0, 0, imgWidth, imgHeight); gl.drawArrays(....); //or gl.drawElements(....); //set the rendered texture to current texture for next frambuffer using gl.bindTexture(gl.TEXTURE_2D, texture[i%2]); }
完整的過程為:
originalTexture --> gray program --> set FBO1 --> draw --> FBO1 --> set texture1 texture1 --> blur program --> set FBO2 --> draw --> FBO2 --> set texture2 texture2 --> edge program --> set FBO1 --> draw --> FBO1 --> set texture1
該過程中,FBO1與texture1是進行色彩渲染綁定的,所以set FBO1后進行渲染則會直接渲染至texture1
當我們完成了整個繪制的時候,要正常顯示處理后的圖片,則要從FBO中跳出來:
//set FBO to null to use default framebuffer gl.bindFramebuffer(gl.FRAMEBUFFER, null);
FrameBuffer的其它用處
gl.readPixels
從FrameBuffer中讀取像素顏色數據
Reference from 《webgl_2.0_reference_card》/《OpenGL ES Reference Pages about readPixels》:
Pixels in the current framebuffercan be read back into an ArrayBufferView object.
void readPixels(int x, int y, long width, long height,enum format, enum type, Object pixels)
x,y
• Specify the window coordinates of the first pixel that is read from the frame buffer. This location is the lower left corner of a rectangular block of pixels.
width,height
• Specify the dimensions of the pixel rectangle.
width
andheight
of one correspond to a single pixel.format
• Specifies the format of the pixel data. The following symbolic values are accepted
RGBA
in WebGLtype
• Specifies the data type of the pixel data. Must be
UNSIGNED_BYTE
in WebGLpixels
• Returns the pixel data.
在使用過程中,我們要先創建pixels對象來儲存數據
//using ArrayBufferView to store pixels data only, Unit8Array is the best because each color data is a byte var pixels = new Uint8Array(ImageWidth * ImageHeight * 4); gl.readPixels(0, 0, ImageWidth, ImageHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
這樣,我們則可以得到整個FBO中的色彩數據
gl.CopyTexImage2D
gl.CopyTexSubImage2D
這兩個函數都是用來從FBO中將數據復制至當前綁定的texture中的
CopyTexImage2D方法:
Reference from 《OpenGL ES Reference Pages about CopyTexImage2D》:
copy pixels into a 2D texture image
void CopyTexImage2D(enum target, int level,enum internalformat, int x, int y, sizei width,sizei height, int border);
target:
• TEXTURE_2D
• TEXTURE_CUBE_MAP_POSITIVE_{X, Y, Z},
• TEXTURE_CUBE_MAP_NEGATIVE_{X, Y, Z}
internalformat:
• ALPHA
• LUMINANCE
• LUMINANCE_ALPHA
• RGB
• RGBA
x,y
Specify the window coordinates of the lower left corner of the rectangular region of pixels to be copied.
width
Specifies the width of the texture image. Must be 0 or 2 n + 2 border for some integer n.
height
Specifies the height of the texture image. Must be 0 or 2 m + 2 border for some integer m.
border
Specifies the width of the border. Must be either 0 or 1.
CopyTexSubImage2D方法:
Reference from 《OpenGL ES Reference Pages about CopyTexSubImage2D》:
copy a two-dimensional texture subimage
void CopyTexSubImage2D(enum target, int level, int xoffset,int yoffset, int x, int y, sizei width, sizei height);
target:
• TEXTURE_2D
• TEXTURE_CUBE_MAP_POSITIVE_{X, Y, Z},
• TEXTURE_CUBE_MAP_NEGATIVE_{X, Y, Z}
level:
Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image.
xoffset:
Specifies a texel offset in the x direction within the texture array.
yoffset:
Specifies a texel offset in the y direction within the texture array.
x,y:
Specify the window coordinates of the lower left corner of the rectangular region of pixels to be copied.
width:
Specifies the width of the texture subimage.
height:
Specifies the height of the texture subimage.
這兩個方法的不同之處相信大家已經看得出來了
CopyTexSubImage2D相對CopyTexImage2D增加了offset來改變復制區域
其最終復制區域為:[x, xoffset + width - 1]與[y, yoffset + height -1]。
而CopyTexImage2D則是比CopyTexSubImage2D多了internelformat參數來控制對像素數據復制的種類。
結語:
有了對texture靈活的操作,則我們才能做出更有趣的東西出來,而framebuffer在里面也是相當重要的一個角色。
附:
WebGL-1.0參考卡片:http://files.cnblogs.com/files/zhiyishou/webgl-reference-card-1_0.pdf
OpenGL-ES-2.0參考卡片:http://files.cnblogs.com/files/zhiyishou/OpenGL-ES-2_0-Reference-card.pdf
OpenGL-ES-2.0參考手冊:https://www.khronos.org/opengles/sdk/docs/man/
The end.