OpenGL ES是在Android, iOS等移动平台上使用的3D图形绘制引擎,其本身是一套API标准,khronos组织负责这套API的设计规范,具体实现交由硬件厂商、模拟器厂商与操作系统厂商来完成,只要求符合其API标准的要求,就可以在不同的硬件上使用相同的API来达到平台一致的显示要求。
图形处理器GPU
图形处理器是用于对图形图像进行处理和输出显示的一种专门用途的芯片,一般它代表着计算机上的高性能/并行性/群核
等等特征,以及厂商差异性,OpenGL/OpenGL ES就是用来统一所有GPU厂商对操作系统和应用程序厂商的一个统一图形接口规范体系,基本上对于现在所有PC设备与移动设备无不支持OpenGL标准。
同时我们可以在支持OpenGL ES移动设备上使用EGL接口层,使得我们可以在本地应用层面上使用EGL接口进行窗口应用开发。让窗口图形应用开发更简便,我们可以将不限于Android的各个平台的OpenGL ES与本地窗口的接口操作API都看作EGL接口,一般来说EGL接口都会提供一个rendering context API提供给我们使用。
相关新闻:Firefox将会在Linux平台上迁移到EGL接口
http://www.linuxeden.com/a/92972
着色器(Shaders)
什么是着色器?
着色器就是OpenGL用于指定界面绘制元素的一系列属性和方法的一种控制屏幕上的像素的程序,这个程序运行在不同厂商的GPU处理器或者模拟处理程序中,它控制着每个像素的颜色、位置、以及其他的高级形态等等,我们不能编写直接运行在不同GPU上的通用程序,但是我们通过着色器脚本GLSL(GL Shading Language),通过操作系统驱动层传入到OpenGL驱动,通过pipeline对脚本进行编译链接与使用。
OpenGL ES绘制流程
OpenGL ES着色器绘制流程
在Android上使用OpenGL ES 2.0
在Android上原生支持OpenGL 1.0, 2.0, 3.0, 3.1等版本,我们以2.0为例。
要在Android上使用OpenGL首先需要在清单文件中指明应用程序本身要用到的OpenGL版本,
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
我们需要在我们的Activitiy 中定义一个GLSurfaceView
来显示我们的GL内容并为之交互。
我们需要一个GLSurfaceView
实例来显示OpenGL的内容
//activity要传入Activity自身的context实例
GLSurfaceView glSurfaceView = new GLSurfaceView(activity);
并且设置
//设置版本号为2
glSurfaceView.setEGLContextClientVersion(2);
//按需渲染,节能减排
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
glSurfaceView.setRenderer(new MyRenderer(this));
上面我们对用于展示OpenGL ES的GLSurfaceView
就算设置完成了。
我们需要对一个类进行实现,定义为内部类也好或者定义一个类实现其接口也好,MyRenderer
类将会是GLSurfaceView.Renderer
的接口实现。
public interface Renderer {
void onSurfaceCreated(GL10 gl, EGLConfig config);
void onSurfaceChanged(GL10 gl, int width, int height);
void onDrawFrame(GL10 gl);
}
onSurfaceCreated
方法用于当OpenGL的Surface被创建时要执行的动作,
onSurfaceChanged
方法用于Surface大小发生变更时要执行的动作,在这个方法中一般会对视口大小进行调整,通过执行gl.glViewport(0, 0, width, height);
方法来实现。
onDrawFrame
方法用于绘制每一帧时对画面状态的一个更新和绘制。这个过程是更新pipeline告知OpenGL绘制流程的过程。一般伴有gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
开头,对整个画布进行清理初始化。
MyRenderer
作为Renderer
的子类,我们的目标就是要实现这三个函数来实现对内容的绘制。
OpenGL ES的坐标系统
OpenGL ES的坐标系统既不是左上角为原点,也不是以左下角为原点,而是以屏幕中央为原点,无论屏幕大小如何,屏幕从左到右和从上到下的范围都是
(
−
1
,
1
)
(-1,1)
(−1,1),是以屏幕中心为原点
(
0
,
0
)
(0,0)
(0,0)的笛卡尔坐标系。
左上角
(
−
1
,
1
)
(-1,1)
(−1,1),右上角
(
1
,
1
)
(1,1)
(1,1)
左下角
(
−
1
,
−
1
)
(-1,-1)
(−1,−1),右下角
(
1
,
−
1
)
(1,-1)
(1,−1)
Vertex Shader 顶点着色器
定一个顶点着色器需要以下几种定义的数据:
- 属性量(
attribute
):使用顶点数组提供的每个顶点数据的定义 - 不变量(
uniform
):顶点着色器所用到的常量数据 - 采样:采样是顶点着色器中可选的数据,通过顶点着色器定义的易总特殊的不变量格式用于表示纹理数据
- 着色器程序:顶点着色器源码或者可执行代码描述在顶点上所有的表现。
- 中间可变量(
vary
):顶点着色器所用到的中间可变量。
vary的意思是
可变的
,uniform的意思是一致的
,attribute
的意思是属性
顶点着色器在GPU中的计算会有很多临时变量和可变量作为计算过程的中间量,但是最终要赋值给三个量,作为GLSL的计算结果:
gl_Position
定义了最终顶点着色器绘制的位置gl_FrontFacing
定义了当前是正面的部分还是背面部分,绘制过程可以依据面向来计算颜色,或者其他资源型操作gl_PointSize
定义了点的大小
这三个内置变量
都是用于着色器接受GLSL脚本的执行结果的量。
一个简单的例子:
// 顶点着色器所使用的不变量
uniform mat4 m_Matrix;
//顶点着色器所使用的属性量
attribute vec4 a_position;
attribute vec4 a_color;
//顶点着色器所使用的的可变量,可变量也是顶点着色器输出量,可以作为其他片元着色器的输入量
varying vec4 v_color;
void main(){
v_color = a_color;
gl_Position = a_position;
gl_PointSize = 20;
}
Fragment Shader 片段着色器/片元着色器
片段着色器又叫做片元着色器,属于不同文献书籍的翻译差异
片元着色器的GLSL也有一些常用的关键字,如下:
vary
可变量: 由顶点着色器生成的输出可变量并且光栅化单元,每个片元使用了插值。uniform
不变量:片元着色器所使用的常量- 采样器:一种特定类型的不变量用于表示被片元着色器使用的uniform不变量。
- 着色器程序
这里面没有属性关键字的使用。片元着色器将会生成颜色并且交给内置变量gl_FragColor
。
在OpenGL ES 2.0的pipeline中,颜色、模板、深度、屏幕坐标信息都会在光栅化过程中的片元操作阶段生成。
光栅化过程
经过OpenGL在GPU中的一些对GLSL翻译处理与图元组配以后,将会对组配的数据进行光栅化过程,光栅化过程交由片元着色器阶段处理,简而言之就是GPU会将我们从SL语言描述的内容转换成图像数据渲染到GPU输出缓冲区中。
看个简单的片元着色器的例子:
//定义中等浮点数精度
precision mediump float;
varying vec4 v_color;//由顶点着色器导出的顶点颜色变量
void main(){
//将导入的v_color赋值给内置变量gl_FragColor
gl_FragColor = v_color;
}
GL Shader Language 语言
上面我已经举出了些许Shader语言脚本的个例,我们此时再看一下其语法。
Shader语法特别简单,类似C语言语法,属性和变量的格式为
[变量类型] [数据类型] [变量名]
语句都以;分号结尾
主程序也是void main {}
包裹主程序体。
不同的是GLSL的结尾一定是给着色器内置变量赋值为结尾,将最终的着色器属性定义传回GPU处理。
数据传输的集装箱——FloatBuffer
浮点缓冲区类型是用于保存浮点类型的缓冲区的类,是Buffer的子类,这意味着他也有NIO当中Buffer的诸多可用的对缓冲空间的指针方法,这部分都是Buffer当中的通用函数,比如
capacity()
返回缓冲区容量position()
返回缓冲区当前的指针位置position(int newPosition)
指定缓冲区位置指针limit()
返回缓冲区上限limit(int newLimit)
设定新的缓冲区上限mark()
设定指针标记为当前的positionreset()
重置到指针mark的位置clear()
位置归零,上限设定为容量值,取消mark标记flip()
翻转缓冲区,当前位置指针被设定为上限,当前位置归零,取消mark标记rewind()
回卷,位置归零,取消mark标记
……
等等关键方法,这里只列举常用的一些方法和说明。
通过ByteBuffer
方法分配直接内存
,并且指定本地字节序,并且指定为FloatBuffer
类型,就可以作为顶点数据缓冲区来使用了。
//分配Direct内存的大小一般是数组大小 x 数据类型长度
public static final int BYTES_PER_FLOAT = 4;
FloatBuffer vertexData = ByteBuffer
.allocateDirect(length * BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
Android OpenGL ES API
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLES20.*;
以上三个命名空间下的一些常用API
GL10:
API | 参数 | 说明 |
---|---|---|
glClearColor | float red,green,blue,alpha | 初始清屏颜色 |
glClear | int mask | 清屏动作glClear(GL10.GL_COLOR_BUFFER_BIT) |
glViewport | int x, int y, int width, int height | 定义视口尺寸 |
程序编译相关API
GLES20
API | 参数 | 说明 | 返回 |
---|---|---|---|
glCreateShader | int type | 参数通过GL_VERTEX_SHADER 和GL_FRAGMENT_SHADER 选择使用哪种着色器 | 返回着色器对象ID |
glShaderSource | int shader, String glsl | 着色器对象ID 和 GLSL脚本 | |
glCompileShader | 对指定的着色器传入代码, 参数是着色器对象ID 和glsl代码,对指定的着色器对象ID进行编译 | ||
glGetShaderiv | int shader, int pname, int[] params, int offset | 获取着色器信息: shader是着色器对象ID,pname是要获取的着色器属性 ,params参数数组,offset偏移量 | |
glGetShaderInfoLog | int shader | 着色器对象ID | 返回着色器日志 |
glDeleteShader | int shader | 特定的着色器对象ID 删除着色器 | |
glCreateProgram | 创建着色器程序 | 返回创建的程序ID | |
glAttachShader | int program, int shader | 对特定的程序ID附加着色器 | |
glLinkProgram | int program | 对特定的程序ID执行链接操作 | |
glGetProgramiv | int program, int pname, int[] params, int offset | 获取程序信息 program是程序对象ID,pname是要获取的程序属性 ,params参数数组,offset偏移量 | |
glValidateProgram | int program | 对特定的编译好的程序ID执行验证操作 | |
glGetProgramInfoLog | int program | 获取程序信息日志 | |
glUseProgram | int program | 使用程序 | |
glGetUniformLocation | int program , int name | 获取指定名称的uniform变量的位置 | |
glGetUniformLocation | int program , int name | 获取指定名称的attribute变量的位置 | |
glVertexAttribPointer | int indx, int size, int type, boolean normalized, int stride, java.nio.Buffer ptr | 指定程序传入顶点属性指针: indx属性位置指针,size 位置数量, normalized是否标准化,stride,ptr数组缓冲区 | |
glEnableVertexAttribArray | int indx | 启用顶点属性 |
着色器程序编译流程
着色器在真正被OpenGL使用并且推到FrameBuffer之前,需要对脚本进行编译
首先需要将代码经过函数glShaderSource
传入着色器程序,然后编译着色器glCompileShader
,如果没有出错的话,接下来调用glCreateProgram
创建着色器程序,此时程序中没有着色器,我们将着色器附着到程序上,通过glAttachShader
将着色器附着到程序上,最后链接程序,我们完成了对着色器程序的编译工作,而这些工作都是在运行时完成的。