概览
Android通过Open Graphics Library(OpenGL®),特别是OpenGL ES API包括对高性能2D和3D图形的支持。OpenGL是一种跨平台的图形API,它为3D图形处理硬件指定了标准的软件接口。OpenGL ES是用于嵌入式设备的OpenGL规范的一种形式。Android支持以下几种版本的OpenGL ES API:
- OpenGL ES 1.0 and 1.1,从1.0起就开始支持;
- OpenGL ES 2.0,从Android2.0(API Level 8)开始支持;
- OpenGL ES 3.0,从Android4.3(API Level 18)开始支持;
- OpenGL ES 3.1,从Android5.0(API Level 21)开始支持;
也就是说目前主流Android 4.4以上的设备都可以使用OpenGL ES 3.0了。
系列文章
- Android OpenGL基础1——常用概念及方法解释
- Android OpenGL基础2——Shader的使用流程
- Android OpenGL基础3——纹理的使用
- Android OpenGL基础4——变换
- Android OpenGL——Android Studio OpenGL开发的简单示例
关于OpenGL一些概念的详细说明,参见下面文章:
注意:设备能支持OpenGL ES API的前提是设备制造商提供了相应管道的实现,不然一切免谈。
基础
Android中开发OpenGL的流程:
- OpenGL运行准备工作(也就是GLSurfaceView的设置工作)
- Shader源码的编写及编译及对象创建;
- Program的创建,Shader的挂在,Program的编译链接使用及销毁等;
- 调试运行程序
GLSurfaceView
GLSurfaceView是OpenGL在Android中的载体(因为OpenGL是一个图形库,但它没有实现自己的图形窗口话系统,需要依赖于当前环境来提供它渲染的载体,Android中这一工作就是通过GLSurfaceView来实现的。
Android 中主要通过GLSurfaceView以及GLSurfaceView.Rendereer来使用OpenGL ES API
- GLSurfaceView,继承自SurfaceView,采用一个专门的Surface来展示OpenGL的渲染,有以下特性:- 管理Surface,这是可以 合成到Android视图系统中的特殊内存空间;
- 管理EGL显示,使OpenGL能够渲染到Surface中;
- 接受用户提供的渲染对象,进行实际的渲染;
- 在专用线程上进行渲染,以将渲染性能与UI线程分离;
- 支持按需渲染和连续渲染;
- 可选地包装,跟踪和/或错误检查渲染器的OpenGL调用;
 
- 管理
OpenGL ES 绘制图形
OpenGL支持绘制的图形包括三种
- 点
- 线
- 三角形
其他复杂的图形都是通过上面三种基础图形来绘制的。
坐标系统
OpenGL坐标轴正方向定义:
朝右为X轴正方向,朝上为Y周正方向,朝外为Z轴正方向
OpenGL中,我们最终看到的坐标系统是展示在标准化设备坐标NDC(Normalized Device Coordinate)中。这个坐标系统中,所有的坐标值都在(-1,1)之间,坐标原点相当于是在正方体的中心。所以将OpenGL ES的坐标系映射到Android设备时,如果不进行矩阵变化,就会变形。这个变形就是一个3D->2D的过程;经历的坐标矩阵变化就是如下过程:
- 物体在局部空间(Local Space)存在一个对应的局部坐标(Local Coordinate);
- 然后这个局部坐标(Local Coordinate)会转换成世界坐标(Word Coordinate);
- 然后世界坐标将转换成观察空间坐标(View Coordinate)(即每个坐标点都是相对于摄像机或观察者的);
- 得到观察控件坐标(View Coordinate)后,我们需要按实际情况通过投影矩阵(Projection Matrix)进行变换得到裁剪坐标(Clip Coordinate),这一步会确认那些点最终会出现在屏幕上;
- 最后,裁剪坐标会被转换成屏幕坐标,通过视口变换(Viewport Transform)将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。
所以,一个OpenGL物体需要经过四次矩阵变换才能最终显示在终端屏幕上。
小结参考:
一般步骤
一般步骤见下图:

- Create Program,对应glCreateProgram
- Load Shader,加载Shader,主要包括下满三步:- Create Shader,对应glCreateShader(shaderType),创建shader对象,shaderType在源码中定义了两种,分别是:- GL_VERTEX_SHADER,顶点Shader;
- GL_GEOMETRY_SHADER,几何Shader,顶点到片段之间的过渡;
- GL_FRAGMENT_SHADER,片段Shader;
 
- Source Shader,对应glShaderSource(GLuint shader, GLsizei count, const GLchar * const *string, const GLint *length),替换Shader对象中的Shader 源程序
- Compile Shader,对应glCompileShader(shader),编译Shader源码
- 获取Shader对象相关的信息,对应glGetShaderiv,查询Shader对象的参数信息,支持查询以下信息:- GL_SHADER_TYPE, 获取Shader的类型;
- GL_DELETE_STATUS,获取Shader是否已经删除;
- GL_COMPILE_STATUS,获取Shader是否已经编译;
- GL_INFO_LOG_LENGTH,获取Shader的日志信息;
- GL_SHADER_SOURCE_LENGTH,获取Shader的源码长度;
 
 
- Create Shader,对应
- Attach Shader,对应glAttachShader,将Shader和Program attach起来;
- Link Program,对应glLinkProgram,链接Program对象;
- 获取Program对象的相关信息,对应glGetProgramiv,该方法和glGetShaderiv类似,具体支持的信息可以查看文档;
- Use Program,对应glUseProgram,开始渲染Program对象的内容了(Installs a program object as part of current rendering state);
- Dettach Shader,对应glDettachShader,从Program中detach Shader;
- Delete Shader,对应glDeleteShader,删除 Shader对象;
- Delete Program,对应glDeleteProgram,删除Program对象;
Program
调用glCreateProgram可以小创建一个Program,OpenGL中Program相当于当前渲染管线所使用的程序,是Shader的容器,可以挂载多个Shader。Program相关的函数有:
- glCreateProgram,创建Program对象;
- glAttachShader,将Program和Shader绑定;
- glLinkProgram,链接Program;
- glUseProgram,使用Program;
- glDeleteProgram,删除Program;
辅助函数:
- glGetProgramiv,获取Program对象,然后从Program随想根据key-value形式来获取随想信息;
- glGetProgramInfoLog,获取Program对象的log信息
Shader
基本概念
Shader其实就是一段C程序,在按一定的语法编写好Shader源代码后,需要经过以下步骤得到Shader对象的句柄,然后通过该句柄将其与Program绑定后使用。
- glCreateShader,创建Shader对象;
- glShaderSource,替换Shader对象中的Shader源码;
- glCompileShader,编译Shader源码;
- glDettachShader,将Shader从Program解绑;
- glDeleteShader,删除Shader对象;
Shader基本信息
类型
主要分一下三类:
- GL_VERTEX_SHADER,顶点Shader;
- GL_GEOMETRY_SHADER,几何Shader,顶点到片段之间的过渡;
- GL_FRAGMENT_SHADER,片段Shader;
属性变量与统一变量
在Shader中,属性变量和统一变量由应用程序设置,Attribute属性变量用于传递顶点信息,而Uniform统一变量则用于传递用户自定义的变量。这两种变量在Shader中会被定义为全局变量,在OpenGL中要设置这两种变量,就需要先获取它们的地址,然后调用OpenGL相关的设置接口为它们赋值。
属性变量设置
获取属性变量地址
| 1 | /** | 
通过上面的方法获取到变量的位置后,才能通过调用glVertexAttribXx系列方法来设置设个属性变量的值。
设置属性变量
第一步,假设属性变量的名字为:attrA,要设置这个值,先或去这个变量的位置 locationAttrA:
| 1 | GLint locationAttrA = glGetAttribLocation(program, "attrA"); | 
第二步,在渲染开始前进行属性变量的赋值,这里有两种方式:
- 第一种是在glBegin()和glEnd()中间,在使用glVertex系列函数生成顶点前。先调用glVertexAttrib系列函数进行赋值,接下来生成的顶点会绑定前面设置的属性变量。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10- glBegin(GL_TRIANGLE_STRIP); 
 glVertexAttrib1f(locationAttrA, 1.0f);
 glVertex2f(0.0f, 0.0f);
 glVertexAttrib1f(locationAttrA, 2.0f);
 glVertex2f(0.0f, 1.0f);
 glVertexAttrib1f(locationAttrA, 3.0f);
 glVertex2f(1.0f, 1.0f);
 glVertexAttrib1f(locationAttrA, 4.0f);
 glVertex2f(1.0f, 0.0f);
 glEnd();
- 第二种是使用顶点数组渲染时,这个得先激活属性变量数组的功能,激活方法: - 1 - void glEnableVertexAttribArray(GLint locationAttrA); - 开启这个功能后,需要调用 - glVertexAttribPointer()方法,将属性变量的值批量传入,属性变量的数组和顶点数组是一一对应的。先看看该方法的说明:- 1 
 2
 3
 4
 5
 6
 7- //参数local,属性变量的位置 
 //参数size,属性变量的分量数量,必须为1~4,如1为float、2~3为vec2~3
 //参数type,属性类型,如GL_FLOAT
 //参数normalized,是否对传入的值执行一次归一化操作
 //参数stride,顶点数组中,两个顶点之间的步幅,0表连续的顶点
 //参数pointer,属性变量列表指针,与顶点数组中的顶点一一对应
 void glVertexAttribPointer(GLint local, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer);- 使用示例如下: 
| 1 | //定义4个顶点 | 
统一变量设置
属性变量相当于每个顶点的私有只读变量,而Uniform统一变量则相当于整个Program的全局只读变量。统一变量和属性变量一样,都是先获取变量的位置,然后调用相关的接口进行设置的。不过统一变量在绘制时不能修改,所以必须在绘制前设置它的值。
至于设置接口和属性变量一致,只是将方法名中的Attrib或VertexAttrib替换成Uniform。统一变量的设置比属性变量要轻松得多,因为不需要想办法绑定到每个顶点上,只需要在渲染之前进行设置就可以了。
基本语法
VertexShader示例说明
| 1 | // 指定OpenGL ES的版本,没有写的话默认是2.0 | 
说明:
- 顶点着色器中,输入变量通常存储位置、法线、纹理坐标和颜色这样的数据,数据由应用程序加载; 
- gl_Position,无需in、out、uniform等关键字的声明而直接使用,原来它是默认是归一化的裁剪空间坐标,xyz各个维度的范围为-1到1,仅能在顶点着色器中使用,既是输入也是输出。- gl_Position赋值范围就是float的取值范围(32位),只不过只有[-1,1]区间的片元被绘制。它是vec4类型的,不能重声明为dvec4等类型。
- 插值限定符,主要有三种,分别为:smooth、flat、centroid,说明如下: - smooth,默认方式,平滑着色,顶点着色器的输出变量在图元中线性插值; 
- flat,平面着色,将一个顶点视为驱动顶点(取决于图元类型),该顶点的值用于图元中所有片段; 
- centroid,质心采样,使用多重采样渲染时,该限定符可用于强制插值发生在被渲染图元内部,否则图元边缘可能出现伪像; 
 
FragmentShader示例说明
| 1 | // 指定版本 | 
说明:
- 精度限定符,有lowp(低精度)、mediump(中精度)和highp(高精度)三种,较低的精度效率更高,较高的精度效果更高,通过precision来指定精度,通常:- 顶点着色器中,如果没有指定默认的精度,则int和float默认都是highp;
- 片段着色器中,浮点值没有默认的精度,必须由开发者指定;
 
- 片段着色器的输入一般都是来自顶点着色器的输出;
- 片段着色器通常会输出一个颜色(单目标渲染)或多个颜色(渲染多个目标MRT),具体的对应关系可以通过限定符layout来指定;
小结参考:
小结
本人主要介绍了以下Android OpenGL 中Shader相关的基础知识,大多是参看上面的那位大佬的文章,然后结合者自己看看OpenGL的文档,这样就当结识了OpenGL的Shader了.
编译错误及常见问题
- Expected token ‘{‘, found ‘identifier’? - 通常是GLSL语法错误,看看是不是GLSL内部类写错了; 
- 在给Shader中的统一变量(Uniform)变量赋值时,需要先激活或使用Shader(即调用glUseProgram方法); 
参考文章: