Android OpenGL基础(六、帧缓冲) | 您所在的位置:网站首页 › opengl基础 › Android OpenGL基础(六、帧缓冲) |
一、图形渲染管线流程
经过前面几张的学习后,我们对OpenGL基础用法已经有了初步理解,现在来介绍下图形渲染管线流程,为OpenGL进阶知识做好准备。
OpenGL的图形渲染管线(Graphics Pipeline)是指:将一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。主要流程如下:
下面介绍OpenGL进阶知识:帧缓冲。了解帧缓冲后,可以实现多级滤镜、镜面、离屏渲染、相机帧缓存等高级效果。 在《Android OpenGL基础(四、图片后处理)》一文中,我们介绍了图片滤镜的实现方法,如果要实现多级滤镜(比如先灰度滤镜再边缘滤镜)该如何处理。一种简单的方法是把多个滤镜的片段着色器代码都放在一起,但是这种实现方法不够灵活,代码耦合严重且不能实现自由组合滤镜。下面介绍帧缓冲的实现方式。 2.1 帧缓冲对象在前面章节的OpenGL例子中,在设置了顶点缓冲后,我们又设置了颜色缓冲或者纹理等,而颜色缓冲或者纹理等之所以可以展示出来,是因为它们都是在默认帧缓冲的渲染缓冲上进行的。可以理解为帧缓冲(Framebuffer Object, FBO)是一种可以缓冲颜色、纹理等渲染指令结果的高级缓冲。帧缓冲可以将纹理、渲染缓冲对象(Renderbuffer Object)作为其附件,在帧缓冲激活后,渲染指令将会写入到帧缓冲的附件中。 一个完整的帧缓冲需要满足以下的条件: 附加至少一个缓冲(颜色、深度或模板缓冲)。 至少有一个颜色附件(Attachment)。 所有的附件都必须是完整的(保留了内存)。 每个缓冲都应该有相同的样本数。OpenGL提供了自定义帧缓冲的方法,因此实现多级滤镜可以通过自定义多级帧缓冲,每个帧缓冲附加一个纹理附件,这样每个帧缓冲对应一个滤镜实现,并将结果传递给下一个帧缓冲作为下一个缓冲的纹理,实现多级滤镜自由组合。 2.2 基本用法创建帧缓冲比较简单,此外在创建完后需要给帧缓冲附加一个附件。附件是一个内存位置,它能够作为帧缓冲的一个缓冲,可以将它想象为一个图像。 2.2.1 帧缓冲附件帧缓冲(Framebuffer Object, FBO)的附件有两种类型: 纹理; 渲染缓冲对象(Renderbuffer Object):渲染缓冲对象(Renderbuffer Object)是在纹理之后引入到OpenGL中。渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。由于渲染缓冲对象通常都是只写的,常用于深度和模板附件(适合于大部分时间只写,而不需要从缓冲中读取值)。选择哪种附件通常的规则是,如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。如果你需要从缓冲中采样颜色或深度值等数据,那么应该选择纹理附件。 2.2.2 创建帧缓冲本文主要介绍以纹理作为附件的帧缓冲对象。创建方式如下: // 创建纹理,后面会把这个纹理附加到帧缓冲上 int[] texFBO = {0}; GLES20.glGenTextures(1, texFBO, 0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texFBO[0]); GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); // 创建帧缓冲 int[] FBO = {0}; GLES20.glGenFramebuffers(1, FBO, 0); // 绑定帧缓冲 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, FBO[0]); // 把纹理作为帧缓冲的附件 GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texFBO[0], 0); Log.d(LOGTAG, "initFBO error status: " + GLES20.glGetError()); // 检查帧缓冲是否完整 int FBOstatus = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); if (FBOstatus != GLES20.GL_FRAMEBUFFER_COMPLETE) Log.e(LOGTAG, "initFBO failed, status: " + FBOstatus); 复制代码 2.2.3 渲染到纹理在创建了帧缓冲及附加到其中的纹理后,接下来看怎么渲染到帧缓冲内的纹理,以及如何把帧缓冲的纹理渲染到屏幕上。要渲染到自定义FBO,只需要绑定当前FBO即可,在绑定到GL_FRAMEBUFFE目标之后,所有的读取和写入帧缓冲的操作将会影响当前绑定的帧缓冲。如果要渲染到屏幕上,则绑定到默认缓冲帧即可。 // 绑定自定义缓冲帧 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers.get(i)) // 绑定默认缓冲帧 GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0) 复制代码 2.3.4 绘制流程 在加入自定义帧缓冲后的绘制流程与只有默认帧缓冲的流程对比如下:
在不需要帧缓冲时,调用如下方法删除帧缓冲列表: GLES20.glDeleteFramebuffers(frameBuffers.length, frameBuffers, 0); 复制代码 三、相机帧缓冲同样地,对于相机也可以采用帧缓冲方案,来实现更复杂的效果。可以将OpenGL相机基础改造为自定义FBO的方式来实现相机预览。在一些复杂的项目中,需要用FBO实现相机预览,并在此基础上加入多级FBO来实现自由扩展相机滤镜或其他绘制需求。实现原理比较简单:在获得相机数据后,首先将相机纹理绘制到FBO,然后依次绘制多级FBO,最终绘制到默认帧缓冲进行上屏展示。 3.1 相机与FBO需要注意的是,在获取到相机帧后,相机帧的顶点坐标与2D图片不同(具体参考"相机预览及滤镜")。首先,分别用不同程序表示相机纹理绘制和2D纹理绘制程序,相机的OES着色器和用于FBO的2D纹理片段着色器GLSL代码如下: class CameraGLRenderer(private val frameAvailableListener: SurfaceTexture.OnFrameAvailableListener) : GLSurfaceView.Renderer { // 顶点着色器 private val vertexShaderCode = """attribute vec2 vPosition; attribute vec2 vTexCoord; varying vec2 texCoord; void main() { texCoord = vTexCoord; gl_Position = vec4 ( vPosition.x, vPosition.y, 0.0, 1.0 ); }""" /** * 片段着色器;用于相机纹理; */ private val fssOES = """#extension GL_OES_EGL_image_external : require precision mediump float; uniform samplerExternalOES sTexture; varying vec2 texCoord; void main() { gl_FragColor = texture2D(sTexture,texCoord); }""" /** * 片段着色器;用于2D的FBO */ private val fss2D = """precision mediump float; uniform sampler2D sTexture; varying vec2 texCoord; void main() { gl_FragColor = texture2D(sTexture,texCoord); }""" // 顶点着色器;四边形顶点坐标 private val squareCoords = floatArrayOf(-1f, -1f, -1f, 1f, 1f, -1f, 1f, 1f) // 相机纹理的四个点坐标 private val texCoordOES = floatArrayOf(0f, 1f, 0f, 0f, 1f, 1f, 1f, 0f) // 2D纹理的四个点坐标 private val texCoord2D = floatArrayOf(0f, 0f, 0f, 1f, 1f, 0f, 1f, 1f) // 四个顶点的缓冲数组 private val vertexBuffer: FloatBuffer = ByteBuffer.allocateDirect(squareCoords.size * 4).order(ByteOrder.nativeOrder()) .asFloatBuffer().apply { put(squareCoords) position(0) } // 相机纹理顶点的缓冲数组 private val oESvertexBuffer: FloatBuffer = ByteBuffer.allocateDirect(texCoordOES.size * 4).order(ByteOrder.nativeOrder()) .asFloatBuffer().apply { put(texCoordOES) position(0) } // 2D纹理顶点的缓冲数组 private val texture2DvertexBuffer: FloatBuffer = ByteBuffer.allocateDirect(texCoord2D.size * 4).order(ByteOrder.nativeOrder()) .asFloatBuffer().apply { put(texCoord2D) position(0) } /** * OES着色器程序ID引用 */ private var mOESProgram = 0 /** * FBO用到的2D着色器程序 */ private var m2DProgram = 0 init { // 编译顶点着色器和片段着色器 val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode) val oESfragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fssOES) val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fss2D) // glCreateProgram函数创建一个着色器程序,并返回新创建程序对象的ID引用 mOESProgram = GLES20.glCreateProgram().also { // 把顶点着色器添加到程序对象 GLES20.glAttachShader(it, vertexShader) // 把片段着色器添加到程序对象 GLES20.glAttachShader(it, oESfragmentShader) // 连接并创建一个可执行的OpenGL ES程序对象 GLES20.glLinkProgram(it) } m2DProgram = GLES20.glCreateProgram().also { GLES20.glAttachShader(it, vertexShader) GLES20.glAttachShader(it, fragmentShader) GLES20.glLinkProgram(it) } } } 复制代码 3.2 初始化FBO在onSurfaceChanged中创建FrameBuffer,设置FrameBuffer的宽高: class CameraGLRenderer(private val frameAvailableListener: SurfaceTexture.OnFrameAvailableListener) :GLSurfaceView.Renderer { // FBO引用 private val FBO = intArrayOf(0) // 相机纹理 private val texCamera = intArrayOf(0) // FBO的2D纹理 private var texFBO:IntArray = intArrayOf(0) // private var texDraw:IntArray = intArrayOf(0) override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) { initFBO(width, height) } /** * 初始化FBO **/ private fun initFBO(width: Int, height: Int) { // 创建相机纹理 GLES20.glGenTextures(1, texCamera, 0) GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texCamera[0]) GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE) GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE) GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST) GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST) mSTexture = SurfaceTexture(texCamera[0]) mSTexture?.setOnFrameAvailableListener(frameAvailableListener) // 创建 GLES20.glGenTextures(1, texDraw, 0) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texDraw[0]) GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST) // 创建FBO的纹理 GLES20.glGenTextures(1, texFBO, 0) GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texFBO[0]) GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST) GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST) // 创建FBO GLES20.glGenFramebuffers(1, FBO, 0) GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, FBO[0]) GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texFBO.get(0), 0) val FBOstatus = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) mFBOWidth = width mFBOHeight = height } } 复制代码 3.3 绘制流程自定义CameraTextureListener接口来实现多级滤镜: public interface CameraTextureListener { /** * **/ public boolean onCameraTexture(int texIn, int texOut, int width, int height); } 复制代码绘制流程如下: class CameraGLRenderer(private val frameAvailableListener: SurfaceTexture.OnFrameAvailableListener) : GLSurfaceView.Renderer { override fun onDrawFrame(gl: GL10?) { if (mUpdateST) { mSTexture?.updateTexImage() mUpdateST = false } GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) // 滤镜集合list val texListener: CameraTextureListener = getCameraTextureListener() if (texListener != null) { // texCamera(OES) -> texFBO drawTex(texCamera[0], true, FBO[0]) // call user code (texFBO -> texDraw) val modified = texListener.onCameraTexture(texFBO[0], texDraw[0], mCameraWidth, mCameraHeight) if (modified) { // texDraw -> screen drawTex(texDraw[0], false, 0) } else { // texFBO -> screen drawTex(texFBO[0], false, 0) } } else { // texCamera(OES) -> screen drawTex(texCamera[0], true, 0) } } private fun drawTex(tex: Int, isOES: Boolean, fbo: Int) { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo) if (fbo == 0) { GLES20.glViewport(0, 0, mView.getWidth(), mView.getHeight()) } else { GLES20.glViewport(0, 0, mFBOWidth, mFBOHeight) } GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT) if (isOES) { GLES20.glUseProgram(progOES) GLES20.glVertexAttribPointer(vPosOES, 2, GLES20.GL_FLOAT, false, 4 * 2, vert) GLES20.glVertexAttribPointer(vTCOES, 2, GLES20.GL_FLOAT, false, 4 * 2, texOES) } else { GLES20.glUseProgram(prog2D) GLES20.glVertexAttribPointer(vPos2D, 2, GLES20.GL_FLOAT, false, 4 * 2, vert) GLES20.glVertexAttribPointer(vTC2D, 2, GLES20.GL_FLOAT, false, 4 * 2, tex2D) } GLES20.glActiveTexture(GLES20.GL_TEXTURE0) if (isOES) { GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex) GLES20.glUniform1i(GLES20.glGetUniformLocation(progOES, "sTexture"), 0) } else { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex) GLES20.glUniform1i(GLES20.glGetUniformLocation(prog2D, "sTexture"), 0) } GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4) GLES20.glFlush() } } 复制代码 The End欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐 Android OpenGL开发者文档:developer.android.com/guide/topic… opengl学习资料:learnopengl-cn.github.io/ Android OpenGL基础(一、绘制三角形四边形):juejin.cn/post/707675… Android OpenGL基础(二、坐标系统):juejin.cn/post/707713… Android OpenGL基础(三、绘制Bitmap纹理):juejin.cn/post/707967… Android OpenGL基础专栏:juejin.cn/column/7076… |
CopyRight 2018-2019 实验室设备网 版权所有 |