GPUImage是一个开源的图像处理工具,它基于OpenGL ES实现,Git地址为:https://github.com/BradLarson/GPUImage。GPUImage最大的好处是它提供了一套非常好的图像处理架构,并且提供了各种PhotoShop中常见的滤镜的着色器。使用者只需要通过对这些滤镜的组合,即可实现非常好的滤镜效果。安卓版也有一个GPUImage:https://github.com/CyberAgent/android-gpuimage,但是这个版本远远没有iOS版本的强大功能,支持的特性也相对少很多。最近项目中需要在Android上也实现一个滤镜框架,那就顺便详细研究一下GPUImage的源码,然后自己实现一个安卓版啦。
OpenGL ES基础
OpenGL ES是基于OpenGL简化而来的用于嵌入式系统的图像处理框架,基本上所有的嵌入式系统的图像渲染最终都基于OpenGL ES。OpenGL ES提供了强大的图形处理能力,以及完善的上下文让系统能将CPU中的数据传输到GPU中,并且进行渲染。GPUImage也是基于OpenGL ES实现的。因此,了解一些OpenGL ES的基础对于理解GPUImage的源码也是非常有帮助的。
简单的术语:
Vertex(顶点):在OpenGL中,所有的物体都是由顶点拼凑而成的,即使是一个圆或者一个球。每个球也是通过分解成非常小的三角形进行渲染的。OpenGL中,渲染的最小元素包括三角形(GL_TRIANGLES),线(GL_LINES)以及点(GL_POINTS)。因此,他们都需要由顶点来定义他们在坐标系中的位置;
Fragment(片元):在OpenGL中,所有的显示都是由片元来完成的,OpenGL通过着色器为每一个片元设定颜色,然后GPU通过这些片元的颜色进行渲染。Fragment可以说是OpenGL显示的最小单元。
Texture(纹理):纹理是一个包含元素信息的对象,OpenGL通过读取纹理中的每个纹素(Texel)的颜色来给不同的片元进行上色。
Shader(着色器):OpenGL中包含了两种着色器,顶点着色器(VertexShader)以及片元着色器(FragmentShader)。着色器使用的是Shading Language进行书写,相当于是另一门语言了,这里就不具体介绍了,有兴趣的同学可以参考https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.3.30.pdf。
VertexShader:主要作用是确定每个顶点在渲染坐标系中的位置,并且确定每个顶点的纹理位置坐标;
FragmentShader:通过每个顶点的纹理坐标和纹理,给每一个片元进行上色。
坐标系(Coordinate System):OpenGL之所以能够很好地实现3D的效果,就是因为有一个非常完善的坐标系系统,这些坐标系系统可以将一个点从自己的坐标系映射到屏幕中的3D位置。坐标系之间的变换通常通过矩阵进行,包括Model-View-Projection矩阵,简称MVP Matrix。
Model Matrix是将一个点的位置从自己的坐标系映射到周围环境中(world coordinate);
View Matrix是将点的位置从世界坐标系中,通过调整眼睛(或者说摄像头)的位置,展示同一个物体不同方面的景象;
Projection Matrix则是将点的位置最终转化成在屏幕上显示的位置;
由于GPUImage中主要实现的是2D的图片处理,并没有涉及到太多的3D效果,因此基本没有涉及到MVP矩阵变换。
帧缓存(FrameBuffer):我们在移动设备屏幕上看到的显示其实都是一帧一帧的内存。因此OpenGL使用了帧缓存的机制,现在缓存中完成渲染,然后再将渲染好的帧呈现到屏幕上。其实在OpenGL中,还有很多的Buffer,如RenderBuffer, DepthBuffer, Vertex Buffer, IndexBuffer等等,我们在使用到的时候再进行具体介绍。
上下文(EGLContext):EGLContext是OpenGL用来渲染的一个上下文,包含了很多的环境变量。在不同的系统中,都有不同的实现,在iOS中,已经封装好的EAGLContext就非常容易使用了;而在Android中,如果需要自己控制每一个显示的环节的话,则需要自己创建EGLContext。
Shader中的常用术语:
Attribute: 在Shader中,有些属性是针对每个顶点都不同的,比如它们的位置(position),纹理坐标(TextureCoordinate),这些信息必须要在CPU中计算好,然后放入VertexBuffer或者VertexBufferObject中;GPU会在VB或者VBO中读取到这些信息,才能计算出每个顶点显示的位置;
Uniform:不同于Attribute,uniform是对于所有的顶点或者片元都相同的一些参数,比如MVPMatrix,Sampler以及一些其他的环境变量;
Sampler:采样器,OpenGL通过Sampler和TextureCoordinate来确定一个片元的颜色。
OpenGL ES 渲染流程
1. Vertex Specification: 这个过程中,主要是计算出每个顶点在自己坐标系中的位置,并且将顶点的Attributes放置到VertexBuffer中,GPU会通过VertexBuffer中的信息读取到每个顶点的Attribute并进行处理;
2. Vertex Processing: 这个过程主要通过VertexShader以及MVP Matrix获取到每个顶点的位置;
3. Primitive Assembly: 在顶点的信息确定完成了之后,通过Primitive Assembly将这些顶点拼成最终需要显示的图形;
4. Rasterization:栅格化的过程是将图形的立体位置转换成在屏幕上显示的位置的过程;
5. Fragment Processing:通过采样器,纹理坐标给每个片元进行着色;
6. Per-Fragment Operation:在每个片元的颜色确定了之后,还可以对每个片元进行一些操作,比如Stencil Test,Depth Test等等。
这边主要是介绍了一些OpenGL ES的基础,以便更好的理解GPUImage的源代码,现在一切就绪,我们就可以开始撸代码啦!