【声 明】
首先,这一系列文章均基于自己的理解和实践,可能有不对的地方,欢迎大家指正。
其次,这是一个入门系列,涉及的知识也仅限于够用,深入的知识网上也有许许多多的博文供大家学习了。
最后,写文章过程中,会借鉴参考其他人分享的文章,会在文章最后列出,感谢这些作者的分享。
码字不易,转载请注明出处!
教程代码:【Github传送门】 |
---|
目录
一、Android音视频硬解码篇:
二、使用OpenGL渲染视频画面篇
- 1,初步了解OpenGL ES
- 2,使用OpenGL渲染视频画面
- 3,OpenGL渲染多视频,实现画中画
- 4,深入了解OpenGL之EGL
- 5,OpenGL FBO数据缓冲区
- 6,Android音视频硬编码:生成一个MP4
三、Android FFmpeg音视频解码篇
- 1,FFmpeg so库编译
- 2,Android 引入FFmpeg
- 3,Android FFmpeg视频解码播放
- 4,Android FFmpeg+OpenSL ES音频解码播放
- 5,Android FFmpeg+OpenGL ES播放视频
- 6,Android FFmpeg简单合成MP4:视屏解封与重新封装
- 7,Android FFmpeg视频编码
本文你可以了解到
本文将介绍如何使用FBO,FBO可以实现什么效果,以及如何在着色器中使用多个纹理单元。
先来看看利用FBO实现的灵魂出窍效果:
一、FBO与EGL的离屏渲染的区别
上一篇文章,讲解了如何使用EGL,并且提到EGL可以建立一个离屏渲染的缓冲区,这种离屏渲染的方式通常用于模拟整个渲染窗口,比如可以用于FFmpeg软编码,将显示在虚拟窗口中的画面编码成H264。
与此同时,OpenGL也提供另外一种离屏渲染方式,即FBO。FBO不仅可以实现离屏渲染整个OpenGL窗口,也可以用于处理碎片画面,即窗口中的小画面。
关于EGL的离屏渲染,将会在后面关于FFmpeg的文章中使用到,这里暂且不论。
而在视频编辑当中,FBO离屏渲染扮演着很重要的角色,许多的视频滤镜都会用到,接下来就来看看FBO如何使用吧。
二、FBO简介
OpenGL 在渲染到系统窗口之前,都会将数据送到
FBO
上,也就是说,FBO
其实一直在默默的为我们服务。
所以,OpenGL 在一开始就创建了一个默认的 FBO。
FBO:Frame Buffer Object,帧缓存对象。
从名字上看,往往很容易让人误解这是一个缓存空间,但实际上,FBO很重要的在最后面的Object上。这是一个缓存对象,包含了多个缓冲索引
,分别为颜色缓冲(Color buffers)
, 深度缓冲(Depth buffer)
, 模板缓冲(Stencil buffer)
。
之所以说是缓冲索引,是因为FBO并不包含这些缓冲数据,仅仅保存了缓冲数据的索引地址。
FBO和这些缓冲区则通过附着点进行连接。
可以看到FBO中包含了:
1. 多个颜色附着点(GL_COLOR_ATTACHMENT0、GL_COLOR_ATTACHMENT1...)
2. 一个深度附着点(GL_DEPTH_ATTACHMENT)
3. 一个模板附着点(GL_STENCIL_ATTACHMENT)
可以划分为两类:
纹理附着(颜色附着):主要用于将颜色渲染到纹理中。
渲染缓冲对象RBO(Render Buffer Objecgt):主要用于渲染深度信息和模板信息。
在2D中,通常只用到了颜色附着,另外两种附着通常在3D渲染中使用。
上面说了,FBO可用于离屏渲染,下面就来看看如何通过FBO将画面渲染到一个“后台”的纹理中。
这里的后台,指不用于显示到窗口的纹理。
三、如何使用FBO
1. 新建纹理
fun createFBOTexture(width: Int, height: Int): IntArray {
// 新建纹理ID
val textures = IntArray(1)
GLES20.glGenTextures(1, textures, 0)
// 绑定纹理ID
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0])
// 根据颜色参数,宽高等信息,为上面的纹理ID,生成一个2D纹理
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height,
0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null)
// 设置纹理边缘参数
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE.toFloat())
// 解绑纹理ID
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0)
return textures
}
生成一个用于FBO的纹理和普通的纹理其实差不多。
首先,生成一个纹理ID,并绑定到OpenGL中。
其次,给这个纹理ID生成对应的纹理。
这里使用的是
GLES20.glTexImage2D
,在渲染图片纹理的时候,使用的是GLUtils.texImage2D
。
关于创建纹理的宽高问题,这里说明一下:
FBO创建的是一个虚拟的窗口,所以,大小是可以根据自己的需求设置的,可以比实际系统窗口大。为了视频画面比例正常,可以把OpenGL的窗口宽高,以及纹理的宽高都设置为视频的宽高。因此,OpenGL在渲染的时候,我们也把无需再通过矩阵变换来矫正比例,直接拉伸就可以。
最后,设置纹理边缘参数,然后解绑。
2. 新建FrameBuffer
fun createFrameBuffer(): Int {
val fbs = IntArray(1)
GLES20.glGenFramebuffers(1, fbs, 0)
return fbs[0]
}
新建FrameBuffer类似新建纹理ID,最后返回FBO索引
3. 绑定FBO
fun bindFBO(fb: Int, textureId: Int) {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fb)
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
GLES20.GL_TEXTURE_2D, textureId, 0)
}
先绑定上面创建的FBO,接着将FBO和上面创建的纹理通过颜色附着点 GLES20.GL_COLOR_ATTACHMENT0
绑定起来。
4. 解绑FBO
fun unbindFBO() {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_NONE)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
}
解绑FBO比较简单,其实就是将FBO绑定到默认的窗口上。
这里的
GLES20.GL_NONE
其实就是0
,也就是系统默认的窗口的 FBO 。
5. 删除FBO
fun deleteFBO(frame: IntArray, texture:IntArray) {
//删除Frame Buffer
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_NONE)
GLES20.glDeleteFramebuffers(