基于Android P版本分析
OpenGL
OpenGL是一个跨平台的软件接口语言,用于调用硬件的2D、3D图形处理器;
由于只是软件接口,所以具体底层实现依赖硬件设备制造商。
OpenGL ES
OpenGL ES是OpenGL的分支,针对嵌入式设备设计的,裁剪了OpenGL部分不必要的功能;
应用场景
- 游戏;
- 视频播放器、编辑应用;
- 相机、图片处理应用;
- 对图像处理及时性要求比较高的应用;
OpenGL绘制过程
GL是先绘制好图形的轮廓,再将颜色、图案绘制到这个轮廓上;
GL中,只提供了3种基本图形:点、线、三角形。我们所熟知的图形,都是基于这3中基本图形处理拼接合成的;
渲染流程
GL展示到屏幕上的流程可以划分为3部分、7个步骤:
- 确定顶点的位置,通过这些顶点绘制出指定的 图形;
- 为图形上色,可以是纯色、渐变彩色或者是图片纹理;
- 缓冲与展示,将上述的图形加载到帧缓冲区(FrameBuffer)中,再展示到屏幕上;
虽然渲染流程有很多个步骤,但是我们在开发OpenGL相关内容时,重点则是在顶点着色器、片段着色器、将数据传递给着色器3个环节上;
基本概念
状态机
OpenGL是一个状态机,它维持自己的状态,并根据用户调用的函数来改变自己的状态;
根据状态的不同,调用同样的函数也可能产生不同的效果;
在OpenGL中,大多数元素都可以用状态来描述:
- 颜色、纹理坐标、光源的各种参数;
- 是否启用了光照、是否启动了纹理、是否启用了混合、是否启用了深度测试等;
OpenGL会保持状态,除非我们调用OpenGL函数来改变它;
- 比如你用
glEnablexxx开启了一个状态,在以后的渲染中将一直保留并应用这个状态,除非你调用glDisablexxx及同类函数来改变该状态或程序退出。 - 又或者当前颜色是一个状态变量,可以把当前颜色设置为白色、红色或其他任何颜色,在此之后绘制的所有物体都将使用这种颜色,直到把当前颜色设置为其他颜色。
上下文
上面提到的各种状态值,将保存在对应的上下文(Context)中;
OpenGL ES 上下文(EGLContext):管理所有要绘制的OpenGL ES信息;
通过保存这些状态信息到EGLContext中,可以跟踪用于渲染的帧缓存、用于几何数据、颜色等的缓存。还会决定是否使用纹理、灯光等功能以及会为渲染定义当前的坐标系统等;
渲染管道
Graphics Pipelin: 渲染管道,是显示芯片内部(GPU)处理图形信号相互独立的并行处理单元,也就是渲染流程;
在 OpenGL 中,任何事物都在 3D 空间中,而屏幕和窗口却是 2D 像素数组,这导致 OpenGL 的大部分工作都是关于把 3D 坐标转变为适应你屏幕的 2D 像素;
3D 坐标转为 2D 坐标的处理过程是由 OpenGL 的图形渲染管线(Graphics Pipeline,实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程)管理的;
图形渲染管线可以被划分为两个主要部分:
- 把3D 坐标转换为 2D 坐标;
- 把 2D 坐标转变为实际的有颜色的像素;
2D 坐标和像素也是不同的,2D 坐标精确表示一个点在 2D 空间中的位置,而 2D 像素是这个点的近似值,2D 像素受到你的屏幕/窗口分辨率的限制;
图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行;
OpenGL ES 采用服务器/客户端编程模型,客户端运行在 CPU 上,服务端运行在 GPU 上,调用 OpenGL ES 函数的时,由客户端发送至服务器端,并被服务端转换成底层图形硬件支持的绘制命令。
左边的客户端程序通过调用 OpenGL ES 接口,将顶点,着色器程序,纹理,以及其他一些 GL 状态参数传入右边的 GL 服务端, 然后在客户端调用绘制命令的时候, GL 便会将输入的图元,逐一执行渲染管线的每个阶段,然后将每个像素的颜色值写入到帧缓存中, 最后视窗系统就可以将帧缓存中的颜色值显示在屏幕上。 此外,应用程序也可以从帧缓存中读取数据到客户端。
缓存区
OpenGL主要有3种Buffer。
帧缓存区(Frame Buffer)
帧缓存是接收渲染结果的缓存区,为GPU指定存储渲染结果的区域,存储最终渲染输出结果的地方。
它存储着OpenGL ES绘制的每个像素最终的所有信息:颜色、深度和模板值,可以简单的理解为存储屏幕上最终显示的一帧画面的区域,包含多个图像的集合,例如颜色图像、深度图像、模板图像等;
渲染缓冲区(Render Buffer)
渲染缓存存储着呈现在屏幕上的渲染图像,也被称为颜色缓冲区,是帧缓存区(Frame Buffer)的子集。
本质上是存储要显示的颜色。多个纹理对象或多个渲染缓存对象,可通过连接点连接到帧缓存对象上;
缓冲区对象(Buffer Object)
缓冲区对象就是程序员输入到 OpenGL 的数据,分为结构类和索引类的。
结构类被称为”数组缓冲区对象“或“顶点缓冲区对象”(“Array Buffer Object”或“Vertex Buffer Object”),即用来描述模型的数组,如顶点数组、纹理数组等;
索引类被称为“索引缓冲区对象”(“Index Buffer Object”),是对上述数组的索引;
纹理
纹理是一个用来保存图像的色值的OpenGL ES缓存;
纹理最通常的作用是装饰我们的物体模型,可以理解为像是贴纸一样贴在物体表面,使得物体表面拥有图案;
坐标系
OpenGL 渲染管线整个流程中,涉及了多个坐标系变化,看起来非常繁琐。但是针对 2D 图像处理,我们其实不需要关心这些变化,我们只需要了解标准化设备坐标即可。
与Android中的Canvas或者屏幕坐标体系不同(原点为屏幕左上角),GL的坐标起始位置在屏幕中心,(0,0)作为中心点,X坐标从左到右,Y坐标从下到上,在[-1,1]之间取值,再映射到屏幕上。而超出[-1,1]范围的,将不会显示在屏幕上。而无论屏幕是什么大小,什么形状,坐标范围都是不变的;
可编程着色器
从渲染通道流程中可知,其中包含了顶点着色器和片元着色器;
这两个着色器是有先后顺序的,先执行顶点着色器 -> 图元组装 -> 光栅化 -> 片元着色器;
通俗的理解这两个着色器的作用:类似于画画,先打线稿,再上色;
顶点着色器
通过编程确定我们绘制的区域,片元(绘制区域)就是通过描述的顶点来计算生成的;
代码实例:
private static final String VERTEX_CODE = "attribute vec4 aVertexCo;\n"
+ "attribute vec2 aTextureCo;\n"
+ "uniform mat4 uVertexMatrix;\n"
+ "uniform mat4 uTextureMatrix;\n"
+ "\n"
+ "varying vec2 vTextureCo;\n"
+ "\n"
+ "void main(){\n"
+ " gl_Position = uVertexMatrix*aVertexCo;\n"
+ " vTextureCo = (uTextureMatrix*vec4(aTextureCo,0,1)).xy;\n"
+ "}";
顶点着色器业务:
-
矩阵变换位置;
-
计算光照公式生成逐顶点颜色;
-
生成/变换纹理坐标;
总结: 它可以用于执行自定义计算,实施新的变换,照明或者传统的固定功能所不允许的基于顶点的效果;
片元着色器
用于为绘制的区域进行颜色或图像的填充;
代码实例:
private static final String FRAGMENT_CODE = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "varying vec2 vTextureCo;\n"
+ "uniform samplerExternalOES uTexture;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D( uTexture, vTextureCo);\n"
+ "}";
片段/片元着色器业务:
-
计算颜色;
-
获取纹理值;
-
往像素点中填充颜色值(纹理值/颜色值);
总结: 它可以用于图片/视频/图形中每个像素的颜色填充(比如给视频添加滤镜,实际上就是将视频中每个图片的像素点颜色填充进行修改);
图元装配
顶点着色器之后,下一个阶段就是进行图元装配,其中图元分为:点、线、三角形等,而图元装配就是将顶点数据计算成一个个图元,在这个阶段会执行,裁剪、透视分隔和Viewport变换操作;
图元类型和顶点确定将被渲染的单独图元。对于每个单独的图元及其对应的顶点,图元装配阶段执行的操作包括:将顶点着色器的输出值执行裁剪、透视分割、视口变换,之后进入光栅化阶段;
光栅化
光栅化,其实就是把矢量图形转换为像素点的过程;
我们屏幕上显示的都是像素点,但是三维物体都是点、线、面构成的。要让点线面变成能在屏幕上显示的像素,就需要Rasterize这个过程。就是从矢量的点线面的描述,变成像素的描述
在这个阶段绘制对应的图元(点、线、三角形)。光栅化就是将图元转化成一个二维片段的过程,而这些转化的片段将由片元着色器处理,这些二维片段就是屏幕上可绘制的像素;
EGL
EGL 是 OpenGL ES 渲染 API 和本地窗口系统(native platform window system)之间的一个中间接口层,它主要由系统制造商实现。EGL提供如下机制:
- 与设备的原生窗口系统通信
- 查询绘图表面的可用类型和配置
- 创建绘图表面
- 在OpenGL ES 和其他图形渲染API之间同步渲染
- 管理纹理贴图等渲染资源
为了让OpenGL ES能够绘制在当前设备上,我们需要EGL作为OpenGL ES与设备的桥梁。
EGL类
android.opengl.EGL14: android.opengl.EGLConfig:配置参数来获取 EGL支持的EGLConfig; android.opengl.EGLContext:创建上下文环境,存储OpenGL ES绘图的一些状态信息; android.opengl.EGLDisplay:获取默认的显示设备(渲染的目标),并初始化,是对实际显示设备的抽象; android.opengl.EGLExt: android.opengl.EGLSurface:创建EGLSurface来连接EGL和设备的屏幕,用来存储图像的内存区域;
EGL绘图步骤
- 获取 EGL Display 对象:eglGetDisplay();
- 初始化与 EGLDisplay 之间的连接:eglInitialize();
- 获取 EGLConfig 对象:eglChooseConfig();
- 创建 EGLContext 实例:eglCreateContext();
- 创建 EGLSurface 实例:eglCreateWindowSurface();
- 连接 EGLContext 和 EGLSurface:eglMakeCurrent();
- 使用 OpenGL ES API 绘制图形:gl_*();
- 切换 front buffer 和 back buffer 送显:eglSwapBuffer();
- 断开并释放与 EGLSurface 关联的 EGLContext 对象:eglRelease();
- 删除 EGLSurface 对象;
- 删除 EGLContext 对象;
- 终止与 EGLDisplay 之间的连接;