作用
FrameBuffer Object,也稱FBO,離屏渲染,可以擺脫屏幕的束縛,在后臺做圖像處理。
理解
FrameBuffer和Texture綁定,FrameBuffer猶如畫板,而Texture猶如畫紙,我們在上面畫東西,畫完后,我們可以拿Texture去繪制到其他地方上面。
(本文重點:這個是我個人對FBO的理解,也是幫助我去使用它的方式。如果有更好的理解方式,可以留言溝通。)
代碼
本章案例效果是在屏幕外繪制一張圖片,并保存到本地。
由于GL運行需要EGL環境,而GLSurfaceView已經幫我們構建了這樣的一個環境,所以我們此次也是在GLSurfaceView上運行,但是不繪制到屏幕上。
案例為試驗效果,只繪制一幀,所以就放到onDrawFrame上運行,讀者之后可以根據自己的需求,處理好相關的生命周期。
public void onDrawFrame(GL10 glUnused) {
GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// 1. 創建FrameBuffer、紋理對象
createEnv();
// 2. 配置FrameBuffer相關的繪制存儲信息,并且綁定到當前的繪制環境上
bindFrameBufferInfo();
// 3. 更新視圖區域
GLES20.glViewport(0, 0, mTextureBean.getWidth(), mTextureBean.getHeight());
// 4. 繪制圖片
drawTexture();
// 5. 讀取當前畫面上的像素信息
readPixels(0, 0, mTextureBean.getWidth(), mTextureBean.getHeight());
// 6. 解綁FrameBuffer
unbindFrameBufferInfo();
// 7. 刪除FrameBuffer、紋理對象
deleteEnv();
}
以上就是關鍵代碼,相比之前其他章節,這里多出了1、2、6、7這幾個關鍵步驟。
步驟1. 創建FrameBuffer、紋理對象
private int[] mFrameBuffer = new int[1];
private int[] mTexture = new int[1];
private void createEnv() {
// 1. 創建FrameBuffer
GLES20.glGenFramebuffers(1, mFrameBuffer, 0);
// 2.1 生成紋理對象
GLES20.glGenTextures(1, mTexture, 0);
// 2.2 綁定紋理對象
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTexture[0]);
// 2.3 設置紋理對象的相關信息:顏色模式、大小
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,
mTextureBean.getWidth(), mTextureBean.getHeight(),
0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
// 2.4 紋理過濾參數設置
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
// 2.5 解綁當前紋理,避免后續無關的操作影響了紋理內容
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}
創建紋理和之前的沒有差別,而創建Framebuffer也很簡單。
步驟2. 配置FrameBuffer相關的繪制存儲信息,并且綁定到當前的繪制環境上
private void bindFrameBufferInfo() {
// 1. 綁定FrameBuffer到當前的繪制環境上
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]);
// 2. 將紋理對象掛載到FrameBuffer上,存儲顏色信息
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, mTexture[0], 0);
}
這里先將FrameBuffer綁定到當前的繪制環境上,所以,在沒解綁之前,所有的GL圖形繪制操作,都不是直接繪制到屏幕上,而是繪制到這個FrameBuffer上!
若想要解綁,想直接繪制到屏幕上,則可以通過GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);實現。
第二步是將FrameBuffer和紋理對象相關聯,紋理存儲繪制到FrameBuffer上的顏色信息,代碼也很簡單。
步驟6. 解綁FrameBuffer
private void unbindFrameBufferInfo() {
// 解綁FrameBuffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
解綁,之后的繪制操作都是直接繪制到屏幕上。
步驟7. 刪除FrameBuffer、紋理對象
private void deleteEnv() {
GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0);
GLES20.glDeleteTextures(1, mTexture, 0);
}
注意
FrameBuffer每次繪制都會得到一個水平鏡像翻轉的視圖,要處理這個問題,可以在繪制的時候添加一個翻轉矩陣,或者,用FrameBuffer繪制2次。
總結
本章使用FrameBuffer實現了離屏渲染,并且將FrameBuffer上的繪制信息保存成Bitmap到本地(此處省略,詳細可以看GitHub工程),而FrameBuffer除了這個作用外,還可以將離屏渲染好的圖片再繪制到屏幕上,而不用繪制到本地,畢竟我們繪制后得到一個Texture,那就有發揮的空間。比如我們要做的效果是屏幕上畫一個背景,背景上有朵花,一共2張圖,但背景要做濾鏡處理,而花不用,那么,我們可以將背景通過FrameBuffer去做濾鏡處理,然后得到一個紋理,直接繪制到屏幕上,而花直接繪制,那么就得到想要的效果了。具體留給讀者作為練習題。
后記
該篇文章是差不多半年前就寫好的文章,不過覺得寫得比較簡陋,缺少一些圖片來生動形象地去描述這個概念,也怕自己的局限誤導了讀者。不過由于有讀者好奇追問,所以就勉強放上來。文中若有疏漏、誤導,請指出。
閱讀資料
其他
- 本系列課程目錄詳見 簡書 - Android OpenGL ES教程規劃
- 參考資料見Android OpenGL ES學習資料所列舉的博客、資料。
?GitHub工程?
本系列課程所有相關代碼請參考我的GitHub項目?GLStudio?,喜歡的請給個小星星。??