1. 程式人生 > >OpenGL ES 幀緩衝物件(FBO):Render to texture

OpenGL ES 幀緩衝物件(FBO):Render to texture

幀緩衝物件FBO

渲染管線的最後一個階段是到幀緩衝區。前面學習的好多知識所做的渲染操作都是在預設的幀緩衝中進行的,這個預設的幀緩衝是我們建立一個Surface時自動建立和配置好的,這篇部落格就建立我們自己的緩衝區而不使用系統提供的緩衝區,這樣就有了另一種渲染方式,預設情況下,我們使用OpenGL ES使用的視窗系統提供的幀緩衝區,這樣繪製的結果是顯示到螢幕上,然而實際中有很多情況並不需要渲染到螢幕上,那麼使用視窗系統提供的幀緩衝區就不太好了,這個時候使用FBO(Frame Buffer Object)就可以很方便的實現這類需求。

我們知道顯示到螢幕上的每一幀資料其實對應的就是記憶體中的資料,在記憶體中對應分配著儲存幀資料的緩衝區,包括寫入顏色的顏色緩衝,寫入深度值的深度緩衝,以及基於一些條件丟棄片元的模板緩衝,這幾種緩衝一起稱之為幀緩衝。

幀緩衝區物件(FBO)是一組顏色、深度、模板附著點,紋理物件可以連線到幀緩衝區物件的顏色附著點,同時也可以連線到FBO的深度附著點,另外一種可以連線到深度附著點和模板附著點的一類物件叫做渲染緩衝區物件(RBO)

這裡寫圖片描述

建立幀緩衝物件

和建立紋理類似,可以使用glGenFramebuffers函式建立幀緩衝。

public static native void glGenFramebuffers
(int n, int[] framebuffers, int offset);

返回的framebuffers中包含了生成的幀緩衝物件,該幀緩衝物件是不為0的整數,0用來表示視窗系統生成的幀緩衝區,建立完幀緩衝物件後,接下來要將其繫結到當前幀緩衝。

使用glBindFramebuffer函式用於設定當前幀緩衝區。

public static native void glBindFramebuffer(
        int target, // 一般設定為GL_FRAME_BUFFER
        int framebuffer // 幀緩衝區物件
    );

繫結到GL_FRAMEBUFFER

目標後,接下來所有的讀、寫幀緩衝的操作都會影響到當前繫結的幀緩衝。也可以把幀緩衝分開繫結到讀或寫目標上,分別使用GL_READ_FRAMEBUFFERGL_DRAW_FRAMEBUFFER來做這件事。如果繫結到了GL_READ_FRAMEBUFFER,就能執行所有讀取操作,像glReadPixels這樣的函式使用了;繫結到GL_DRAW_FRAMEBUFFER上,就允許進行渲染、清空和其他的寫入操作。大多數時候不必分開用,通常把兩個都繫結到GL_FRAMEBUFFER上就行。

此時建立的幀緩衝物件其實只是一個“空殼”,它上面還包含一些附著,因此接下來還必須往它裡面新增至少一個附著才可以使用。建立的幀緩衝必須至少新增一個附著點(顏色、深度、模板緩衝)並且至少有一個顏色附著點。
一個緩衝區就是一個記憶體空間,可以新增的附著點可以是紋理物件或者渲染緩衝物件

紋理附著

當把一個紋理扶著到FBO上後,所有的渲染操作就會寫入到該紋理上,意味著所有的渲染操作會被儲存到紋理影象上,這樣做的好處是顯而易見的,我們可以在著色器中使用這個紋理。

int [] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
int textureId = textures[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
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_MIRRORED_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_MIRRORED_REPEAT);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, ShapeView.sScreenWidth, ShapeView.sScreenHeight, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_SHORT_5_6_5, null);
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureId, 0);

建立紋理的方式和前面學的一樣,區別是使用了glTexImage2D函式,前面使用GLUtils#texImage2D函式載入一幅2D影象作為紋理物件,這裡的glTexImage2D稍顯複雜,這裡重要的是最後一個引數,如果為null就會自動分配可以容納相應寬高的紋理,然後後續的渲染操作就會儲存到這個紋理上了。

就下來需要將這個紋理附著到幀緩衝中去

public static native void glFramebufferTexture2D(
        int target, // 建立的幀緩衝型別的目標,一般為GL_FRAMEBUFFER
        int attachment, // 附著點,這裡附著的事一個紋理,需要傳入引數為一個顏色附著點
        int textarget, // 希望附著的紋理型別
        int texture, // 附加的紋理物件ID
        int level // Mipmap level 一般設定為0
    );

attachment可以為一下幾個列舉值: GL_COLOR_ATTACHMENT0GL_DEPTH_ATTACHMENTGL_STENCIL_ATTACHMENT分別對應顏色緩衝、深度緩衝和模板緩衝。

除了顏色附著,還可以附加深度和模板附著到幀緩衝物件上。附著深度緩衝可以使用GL_DEPTH_ATTACHMENT作為附著型別,此時紋理的內部型別為GL_DEPTH_COMPONENT(32位深),附著模板緩衝使用GL_STENCIL_ATTACHMENT附著點,對應文理型別為GL_STENCIL_INDEX

最後使用glFramebufferTexture2D函式將2D紋理附著到幀緩衝物件。

public static native void glFramebufferTexture2D(
        int target,  // GL_FRAMEBUFFER
        int attachment, // GL_COLOR_ATTACHMENT、GL_DEPTH_ATTACHMENT或GL_STENCIL_ATTACHMENT
        int textarget, // 紋理目標,和glTeXImage2D中的引數target一致
        int texture,  // 紋理物件
        int level
    );

渲染緩衝物件附著

上面介紹的紋理附著都可以作為幀緩衝物件的附件,除此之外,還有一種可以作為幀緩衝物件的附著的物件為渲染緩衝物件(RBO),類似於紋理,它也是記憶體中的一片區域,渲染緩衝物件的優點是,它以OpenGL原生渲染格式儲存它的資料,因此在離屏渲染到幀緩衝的時候,這些資料就相當於被優化過的了。

int [] renderbuffers = new int[1];
GLES20.glGenRenderbuffers(1, renderbuffers, 0);
int renderId = renderbuffers[0];
GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderId);
GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, ShapeView.sScreenWidth, ShapeView.sScreenHeight);  
GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, renderId);

使用渲染緩衝物件和使用紋理附著類似,使用glGenRenderbuffers函式生成渲染緩衝物件,然後使用glBindRenderbuffer繫結為當前渲染緩衝區,glRenderbufferStorage函式和glTexImage2D很類似,初始化渲染緩衝物件的資料儲存。

同樣最後使用函式glFramebufferRenderbuffer將渲染緩衝物件附著到幀緩衝物件

public static native void glFramebufferRenderbuffer(
        int target,
        int attachment,
        int renderbuffertarget, // 必須為GL_RENDERBUFFER
        int renderbuffer // 渲染緩衝區物件
    );

紋理、渲染緩衝和幀緩衝區物件在使用結束後需要使用glDeleteXXX函式刪除。

在完成所有附著的新增後,需要使用函式glCheckFramebufferStatus 函式檢查幀緩衝區是否完整。

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
  // error
}

渲染到紋理(Render to Texture)

瞭解瞭如何建立幀緩衝物件後,下面就需要將渲染操作作用於我們建立的幀緩衝物件,思路很簡單,在繪製每一幀影象時,先將影象繪製到FBO中,然後再用該紋理繪製到螢幕上,會看到結果和沒有使用FBO效果一樣。而實際上在使用FBO時只是繪製了一個矩形而已,好處是我們拿到了螢幕上要顯示的物體的所有資料,想要對其進行其他額外的操作就方便多了。
渲染到紋理的大致思路如下:

/*================================render2texture================================*/
glGenFramebuffers(1, framebuffers, 0);
glGenTextures(1, textures, 0);
// setTextureParams();
// 
glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, ShapeView.sScreenWidth, ShapeView.sScreenHeight, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_SHORT_5_6_5, null);
glGenRenderbuffers(1, renderbuffers, 0);
glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, ShapeView.sScreenWidth, ShapeView.sScreenHeight);  
// attach to framebuffer
glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureId, 0);
glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, renderId);
// check if correct
if(GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
    Log.i(TAG, "Framebuffer error");
}
// render to texture using FBO
// clear color and depth

// load uniforms for vertex and fragment shader used to render to FBO
set_fbo_texture_shader_and_uniforms();
// drawing commands to the framebuffer object
draw_to_fbo();
/*================================render2window================================*/
// 切換到視窗系統的緩衝區
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
// Use texture to draw to window system provided framebuffer
// draw a quad that is the size of the viewport
//
// set_screen_texture_shader_and_uniforms();
//
draw_screen_quad();

// clearup
GLES20.glDeleteTextures(1, textures, 0);
GLES20.glDeleteFramebuffers(1, framebuffers, 0);
GLES20.glDeleteRenderbuffers(1, renderbuffers, 0);

載入如圖一個obj檔案的立方體,然後通過先渲染到紋理在通過該紋理來繪製到視窗系統,效果和直接繪製到視窗系統是一樣的。但是他們是有本質區別的。可以看到效果
fbo_render2texture

關鍵的程式碼為

public void draw(float[] mvpMatrix, float[] mMatrix) {
    /*================================render2texture================================*/
    // 生成FrameBuffer
    int [] framebuffers = new int[1];
    GLES20.glGenFramebuffers(1, framebuffers, 0);
    int framebufferId = framebuffers[0];
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferId);
    // 生成Texture
    int [] textures = new int[1];
    GLES20.glGenTextures(1, textures, 0);
    int textureId = textures[0];
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
    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_MIRRORED_REPEAT);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_MIRRORED_REPEAT);
    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, ShapeView.sScreenWidth, ShapeView.sScreenHeight, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_SHORT_5_6_5, null);
    // 生成Renderbuffer
    int [] renderbuffers = new int[1];
    GLES20.glGenRenderbuffers(1, renderbuffers, 0);
    int renderId = renderbuffers[0];
    GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderId);
    GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, ShapeView.sScreenWidth, ShapeView.sScreenHeight);  
    // 關聯FrameBuffer和Texture、RenderBuffer
    GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureId, 0);
    GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, renderId);
    if(GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
        Log.i(TAG, "Framebuffer error");
    }
    GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
    int frameBufferVertexShader = loaderShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
    int frameBufferFagmentShader = loaderShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
    mFrameBufferProgram = GLES20.glCreateProgram();
    GLES20.glAttachShader(mFrameBufferProgram, frameBufferVertexShader);
    GLES20.glAttachShader(mFrameBufferProgram, frameBufferFagmentShader);
    GLES20.glLinkProgram(mFrameBufferProgram);
    int fbPositionHandle = GLES20.glGetAttribLocation(mFrameBufferProgram, "aPosition");
    int fbNormalHandle = GLES20.glGetAttribLocation(mFrameBufferProgram, "aNormal");
    int fbTextureCoordHandle = GLES20.glGetAttribLocation(mFrameBufferProgram, "aTextureCoord");
    int fbuMVPMatrixHandle = GLES20.glGetUniformLocation(mFrameBufferProgram, "uMVPMatrix");
    int fbuMMatrixHandle = GLES20.glGetUniformLocation(mFrameBufferProgram, "uMMatrix");
    int fbuLightLocationHandle = GLES20.glGetUniformLocation(mFrameBufferProgram, "uLightLocation");
    int fbuTextureHandle = GLES20.glGetUniformLocation(mFrameBufferProgram, "uTexture");
    GLES20.glUseProgram(mFrameBufferProgram);
    mVertexBuffer.position(0);
    GLES20.glVertexAttribPointer(fbPositionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer);
    mTexureBuffer.position(0);
    GLES20.glVertexAttribPointer(fbTextureCoordHandle, 2, GLES20.GL_FLOAT, false, 2 * 4, mTexureBuffer);
    mTexureBuffer.position(0);
    GLES20.glVertexAttribPointer(fbNormalHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mNormalBuffer);
    GLES20.glEnableVertexAttribArray(fbPositionHandle);
    GLES20.glEnableVertexAttribArray(fbTextureCoordHandle);
    GLES20.glEnableVertexAttribArray(fbNormalHandle);
    GLES20.glUniform3f(fbuLightLocationHandle, 0, 10, 10);
    GLES20.glUniformMatrix4fv(fbuMVPMatrixHandle, 1, false, mvpMatrix, 0);
    GLES20.glUniformMatrix4fv(fbuMMatrixHandle, 1, false, mMatrix, 0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mLoadedTextureId);
    GLES20.glUniform1i(fbuTextureHandle, 0);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertexCount);

    /*================================render2window================================*/
    // 切換到視窗系統的緩衝區
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
    GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
    int vertexShader = loaderShader(GLES20.GL_VERTEX_SHADER, windowVertexShaderCode);
    int fragmentShader = loaderShader(GLES20.GL_FRAGMENT_SHADER, windowFragmentShaderCode);
    mWindowProgram = GLES20.glCreateProgram();
    GLES20.glAttachShader(mWindowProgram, vertexShader);
    GLES20.glAttachShader(mWindowProgram, fragmentShader);
    GLES20.glLinkProgram(mWindowProgram);
    GLES20.glUseProgram(mWindowProgram);
    int positionHandle = GLES20.glGetAttribLocation(mWindowProgram, "aPosition");
    int textureCoordHandle = GLES20.glGetAttribLocation(mWindowProgram, "aTextureCoord");
    int textureHandle = GLES20.glGetUniformLocation(mWindowProgram, "uTexture");
    mSqureBuffer.position(0);
    GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, (2+2) * 4, mSqureBuffer);
    mSqureBuffer.position(2);
    GLES20.glVertexAttribPointer(textureCoordHandle, 2, GLES20.GL_FLOAT, false, (2+2) * 4, mSqureBuffer);
    GLES20.glEnableVertexAttribArray(positionHandle);
    GLES20.glEnableVertexAttribArray(textureCoordHandle);
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
    GLES20.glUniform1i(textureHandle, 0);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    GLES20.glDeleteTextures(1, textures, 0);
    GLES20.glDeleteFramebuffers(1, framebuffers, 0);
    GLES20.glDeleteRenderbuffers(1, renderbuffers, 0);
}

全部程式碼下載

上面使用了RenderBuffer作為深度緩衝附著,可以看到在繪製的時候分為兩部分,第一部分負責渲染到紋理,在建立好我們的FBO後,執行的繪製操作和前面學的沒有任何區別,接下來切換到系統視窗上再進行繪製,第一次繪製到幀緩衝時,使用的紋理物件是一幅圖片,通過渲染,附著到FBO上面的紋理附件就會被填充,這樣在後面往視窗渲染時只需要這個紋理去渲染即可。
需要注意的是,前面說過,必須至少附著一個附件,可以只附著一個顏色附著,深度附著可以不使用,也就是我們建立的FBO只有顏色緩衝區,那麼結果是,渲染到紋理的操作使得FBO裡面只有顏色緩衝區有值,由於沒有深度緩衝區,所有的深度測試都會通過,於是效果就是下面的樣子,和沒有就開啟深度測試的效果是一樣的。

幀緩衝區沒有深度緩衝附著的效果,就如同沒有開啟深度測試。

fbo_without_depth_-buffer

渲染到深度紋理

另外,也可以使用紋理作為深度附著,很簡單,只需要把上面例子中使用RenderBuffer的地方換成紋理即可,可以達到相同的效果。此外,還可以渲染到深度紋理,在繪製到螢幕時,使用深度紋理,效果如下面的樣子,事實上,如果預先知道影象沒有被用作紋理使用時,還是使用渲染到緩衝區比較好可以提高效能。
深度紋理中每個畫素所記錄的深度值是從0 到1 非線性分佈的。精度通常是 24 位或16 位,這主要取決於所使用的深度緩衝區。當讀取深度紋理時,我們可以得到一個0-1範圍內的高精度值。下面的示例展示出了深度值的視覺化。近的物體偏向於黑色,遠的物體偏向於白色。並且近處的變化幅度大,遠處的變化幅度,也就是近處隨著z序的變化深度值變化大,遠處變化小,這也是深度值的非線性特性。

using depth texture

public void draw(float[] mvpMatrix, float[] mMatrix) {
    /*================================render2texture================================*/
    // 生成FrameBuffer
    int [] framebuffers = new int[1];
    GLES20.glGenFramebuffers(1, framebuffers, 0);
    int framebufferId = framebuffers[0];
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebufferId);
    // 生成Texture
    int [] textures = new int[2];
    GLES20.glGenTextures(2, textures, 0);
    int colorTxtureId = textures[COLOR_TEXTURE];
    int depthTxtureId = textures[DEPTH_TEXTURE];
    // 顏色紋理 :紋理作為顏色附著
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, colorTxtureId);
    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_MIRRORED_REPEAT);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_MIRRORED_REPEAT);
    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, ShapeView.sScreenWidth, ShapeView.sScreenHeight, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_SHORT_5_6_5, null);
    // 深度紋理:紋理作為深度附著
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, depthTxtureId);
    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_MIRRORED_REPEAT);
    GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_MIRRORED_REPEAT);
    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_DEPTH_COMPONENT, ShapeView.sScreenWidth, ShapeView.sScreenHeight, 0, GLES20.GL_DEPTH_COMPONENT, GLES20.GL_UNSIGNED_SHORT, null);
    // 關聯FrameBuffer和Texture
    GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, colorTxtureId, 0);
    GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_TEXTURE_2D, depthTxtureId, 0);
    if(GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE)    {
        Log.i(TAG, "Framebuffer error");
    }
    // set_fbo_texture_shader_and_uniforms();
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertexCount);

    /*================================render2window================================*/
    // 切換到視窗系統的緩衝區
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
    GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
    // set_screen_texture_shader_and_uniforms();
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
    // 使用顏色紋理和使用深度紋理
    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, depthTxtureId/*colorTxtureId*/);
    GLES20.glUniform1i(textureHandle, 0);
    GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

    GLES20.glDeleteTextures(2, textures, 0);
    GLES20.glDeleteFramebuffers(1, framebuffers, 0);
}

全部程式碼下載