Android 系统下 OpenGL 三种离屏渲染技术

共 3697字,需浏览 8分钟

 ·

2024-03-20 11:00

经常有同学问我如何将 OpenGL 渲染出的结果保存成mp4文件,今天我们就来聊聊这个话题。

离屏渲染 OpenGL

要想将OpenGL渲染出的结果保存成mp4文件,我们需要使用一种称为离屏渲染的技术。


什么是离屏渲染呢?顾名思意,就是让OpenGL不将渲染的结果直接输出到屏幕上,而是输出到一个中间缓冲区(一块GPU空间),然后再将中间缓冲区的内容输出到屏幕或编码器等目标上,这就称为离屏渲染


使用离屏渲染有什么好处呢? 比如我们可以让OpenGL将渲染的结果先输出到一个帧缓冲区中,然后再把帧缓冲区的内容送给编码器进行编码,最后写到mp4文件中,这样就达到我们的目标了。 当然在将结果保存成mp4文件的同时,还能让它在屏幕上显示出来就更好了,那么如何做到这一点呢?


在Android系统下,有三种方法可以实现,下面我们就来一一介绍一下。

Android下离屏渲染的三种方式

在Android系统下可以使用三种方法实现同时将OpenGL的内容输出给多个目标(屏幕和编码器)。第一种方法是二次渲染法;第二种方法是使用FBO;第三种是使用BlitFramebuffer


首先我们来看看如何通过二次渲染法实现将OpenGL渲染结果送给屏幕和编码器。

二次渲染法

想通过二次渲染法实现OpenGL渲染结果送屏幕和编码器,我们必须使用SurfaceView,而不能使用GLSurfaceView。


之所以如此,是因为GLSurfaceView有自己的专有渲染线程,这虽然减少了开发者使用OpenGL的复杂度,但也同时降低了开发者对EGL的控制力,显得不够灵活,从而无法将OpenGL的渲染结果输出给多个目标。


为了解决这个问题,我们需要使用 SurfaceView+自己创建渲染线程 这个组合,在自己创建的渲染线程中使用EGL API,通过多次渲染并将结果输送给多个目标Surface来实现二次渲染,其架构如下图所示:

da2c39017a5e0b2f1cb4ad65ba4fb2da.webp

在上图中,我们可以看到有 SurfaceView, MediaCodec, Camera, OpenGL/EGL等组件。


其中SurfaceView用于展示OpenGL的渲染结果,而且它还实现了两个重要的事件处理方法,surfaceCreatedsurfaceChanged


surfaceCreated方法是在SurfaceView中的Surface创建好后被调用。在该方法中,首先创建一个渲染线程,并在渲染线程中创建EGL环境。


而surfaceChanged方法是在Surface发生变化时被调用,在该方法中我们会创建FBO环境,关于这方面的内容我在讲解FBO时再向你做详细介绍。


MediaCodec 用于编码,它也有一个Surface用于接收需要编码的数据


Camera 用于采集视频数据,当采集到视频数据后,它会通知渲染线程,渲染线程通过SurfaceTexture从BufferQueue中取走数据,并交由OpenGL处理。


OpenGL/EGL用于渲染,它收到视频帧后调用Shader程序进行渲染,之后将渲染后的结果输出给SurfaceView的Surface ,让其在屏幕上显示;接下来,调用EGL的eglMakeCurrent方法,将默认Surface从SurfaceView的Surface改为MediaCodec的Surface,然后再次调用Shader程序进行渲染,并将渲染后的结果输出给MediaCodec的Surface进行编码。


也就是说,二次渲染就是调用两次Shader程序进行渲染,每次渲染后的结果输送给不同的目标Surface,因此称为二次渲染法。


当然,如果你需要将渲染结果输出给多个目标也是可以的,那就需要调用多次Shader程序,每次将渲染结果输送给不同的目标Surface就好了。

FBO法

上面的方法虽然能够将同一个源输送给不同的目标,但每次都要调用OpenGL进行重绘,效率上确实不高。尤其是当我们要渲染的模型特别复杂的时候,会严重影响效率,有没有更好的解决办法呢?


OpenGL为我们提供了一种高效的办法,即FBO(FrameBufferObject)。下面我们来看一下如何通过FBO高效的向多个目标输送渲染结果,其架构图如下所示:

0fd97988b5be5cb6f06f4e61236447f0.webp

其架构图与我们上面展示的二次渲染架构图很类似,只不过通过OpenGL渲染的结果不再直接送给不同的Surface,而是将结果输出到FBO中,这里你可以先将FBO想像为一块特殊的空间(关于FBO的细节,我会单独写一篇文章进行讲解)。


然后通过另外一种Shader程序(这种Shader程序是专门用于处理FBO纹理的)再次进行渲染。当然,这种Shader程序由于处理的是纹理,所以会比第一次使用的Shader程序效率高很多。


渲染成功后将结果输出给屏幕,然后切换Surface再次调用Shader(第二个Shader),这次渲染的结果将会输送给MediaCodec的Surface进行编码。


因此,通过FBO方法我们只需要对原模型渲染一次,将结果保存到FBO。之后再对FBO中的内容进行多次渲染,通过这种方式来提高效率。


注意,之所以通过这种方式可以提高效率,原因就在于OpenGL对FBO纹理的渲染效率远远高于对原模型的渲染效率。

BlitFramebuffer法

通过上面的描述,我们仍然觉得不够高效。有没有更好的方法,只渲染一次就可以将结果输送给多个目标呢?到了OpenGL3.0出现了一种更高效的方法,即BlitFramebuffer


它是如何工作的呢?其工作架构图所下所示:

a72d02de6d82035e9d97c258dad4c9b9.webp

该方法不再使用FBO做缓存,而是像二次渲染法一样,先将渲染的内容输出到当前Surface中,但并不展示到屏幕上


相当于把当前的Surface当作一个缓冲区,然后切换Surface,此时MediaCodec的Surface变成了当前Surface,接下来利用OpenGL3.0提供的API BlitFramebuffer从原来的Surface拷贝数据到当前Surface中,再调用EGL的eglSwapBuffers将Surface中的内容送编码器编码。


之后再将当前Surface切回原来的Surface,也就是SurfaceView的Surface,同样调用EGL的eglSwapBuffers方法,将其内容显示到屏幕上。


至此,实现了OpenGL仅渲染一次,却可以输出给多个目标,这种方法是最高效的。

小结

本文向你介绍了三种离屏渲染的方法,其中最高效的是BlitFramebuffer方法,但它是在OpenGL3.0才有的API,因此只有OpenGL3.0才可以使用该方法;其次是FBO方法,但其复杂度要比二次渲染法高很多,并且效率是有提高与原模型的复杂高有很大关系,如果原模型复杂度并不高,那么它与二次渲染法的效率并不多。


此外,本篇文章中还有很多细节并没有介绍到,比如如何使用FBO,如何在自己创建的渲染线程中构建EGL环境,如何使用BlitFramebuffer等,这些内容我将在后面的文章中再向你做详细讲解。

参考资料

《系统玩转OpenGL+AI,实现各种酷炫视频特效》

原文链接:https://zhuanlan.zhihu.com/p/676879474


-- END --


进技术交流群,扫码添加我的微信:Byte-Flow



获取相关资料和源码


推荐:

Android FFmpeg 实现带滤镜的微信小视频录制功能

全网最全的 Android 音视频和 OpenGL ES 干货,都在这了

一文掌握 YUV 图像的基本处理

抖音传送带特效是怎么实现的?

所有你想要的图片转场效果,都在这了

面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?

我用 OpenGL ES 给小姐姐做了几个抖音滤镜


项目疑难问题解答、大厂内部推荐、面试指导、简历指导、代码指导、offer 选择建议、学习路线规划,可以点击找我一对一解答。

浏览 47
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报