示例
渲染管线的最后一个阶段是到帧缓冲区。大部分OpenGL所做的渲染操作都是在默认的帧缓冲中进行的,这个默认的帧缓冲是我们创建一个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_FRAMEBUFFER或GL_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_ATTACHMENT0 : 颜色缓冲
- GL_DEPTH_ATTACHMENT : 深度缓冲
- GL_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文件的立方体,然后通过先渲染到纹理在通过该纹理来绘制到窗口系统,效果和直接绘制到窗口系统是一样的。但是他们是有本质区别的。可以看到效果
关键的代码为:
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里面只有颜色缓冲区有值,由于没有深度缓冲区,所有的深度测试都会通过,于是效果就是下面的样子,和没有就开启深度测试的效果是一样的。
渲染到深度纹理
另外,也可以使用纹理作为深度附着,很简单,只需要把上面例子中使用RenderBuffer的地方换成纹理即可,可以达到相同的效果。此外,还可以渲染到深度纹理,在绘制到屏幕时,使用深度纹理,效果如下面的样子,事实上,如果预先知道图像没有被用作纹理使用时,还是使用渲染到缓冲区比较好可以提高性能。
深度纹理中每个像素所记录的深度值是从0 到1 非线性分布的。精度通常是 24 位或16 位,这主要取决于所使用的深度缓冲区。当读取深度纹理时,我们可以得到一个0-1范围内的高精度值。下面的示例展示出了深度值的可视化。近的物体偏向于黑色,远的物体偏向于白色。并且近处的变化幅度大,远处的变化幅度,也就是近处随着z序的变化深度值变化大,远处变化小,这也是深度值的非线性特性。
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);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。