概览
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
10glBegin(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方法);
参考文章: