背景
绘制 3D 图,总觉得是一件很炫酷的事。虽然在项目中一直没有用到过,但是还是想找个时间,实践一下。
绘制二维图形,尽管使用 OpenGL 有它的优势,但是还是感觉有点杀鸡用牛刀的意 思。这里主要是借助对 二维图形 的绘制过程,解释相关概念。
什么是 OpenGL ES
首先,来说明一下 OpenGL ES for Embedded Systems(OpenGL ES)。它是 OpenGL 的子集,用以渲染 2D 、3D 矢量图的跨语言、跨平台的API,这个 API 通常会和 GPU 交互,完成硬件加速渲染。
Android 平台支持不同版本 OpenGL ES 的 API。其中:
- OpenGL ES 1.0 and 1.1 - 该 API 被Android 1.0 及更高版本支持.
- OpenGL ES 2.0 - 该 API 被 Android 2.2 (API level 8) 及更高版本支持.
- OpenGL ES 3.0 - 该 API 被 Android 4.3 (API level 18) 及更高版本支持.
- OpenGL ES 3.1 - 该 API 被 Android 5.0 (API level 21) 及更高版本支持.
为了获取更广泛的设备支持,通常会基于 OpenGL ES 2.0 做开发。本文也是基于该版本展开。
Android 平台提供的基础
Android 框架层提供了两个对象以使用 OpenGL ES API 操作图像:GLSurfaceView 和 GLSurfaceView.Renderer。
GLSurfaceView
GLSurfaceView 继承自 SurfaceView,拥有专用的 surface 以展示 OpenGL 渲染。它提供了一下特性:
- 管理 surface,同时使得 OpenGL 可以在 surface 上渲染;
- 可以使用用户自定义的 Renderer 对象进行实际的渲染工作;
- 渲染线程独立于 UI 线程之外 ;
- 支持在需要时才进行的被动渲染 和 不间断自动进行的主动渲染两种渲染方式;
//设置一下模式,为被动刷新
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
- 调试渲染调用
简单的使用方式如下所示:
//直接创建一个 GLSurfaceView,当然,也可以通过布局文件创建
glSurfaceView = new GLSurfaceView(this) ;
//使用 OpenGL ES 2.0 context.
glSurfaceView.setEGLContextClientVersion(2);
//设置我们自定义的 renderer
glSurfaceView.setRenderer(new MyRenderer());
setContentView(glSurfaceView);
GLSurfaceView.Renderer
负责进行帧渲染。提供的回调函数有:
- onDrawFrame:负责对当前帧的绘制;
- onSurfaceChanged:当 surface 的大小发生变化时候的回调,比如刚创建 surface 的时候,绘制屏幕发生旋转;
- onSurfaceCreated:当 surface 创建或者重建的时候被调用。调用发生在渲染线程开始的时候,或者当 EGL context 丢失的时候(该 context 通常会在设备从睡眠中唤醒的时候丢失)。
注意,当 EGL context 丢失,和 context 关联的所有 OpenGL 自愿将会被自动删除。
管线渲染过程
要明白这个过程,首先要知道什么是管线。所谓管线,就是在显卡上执行的将数据源转换投射到屏幕像素点上的过程。也就是将我们通过顶点定义的形状,显示到屏幕上。
管线渲染-1如上图所示,
- 定义顶点;
- 通过 vertexShader着色器 告知 GPU 顶点的位置等属性;
- 通过图元装配,生成要绘制的形状;
- 光栅化处理,将所有的点转化为片元(fragment);
- 通过 fragmentShader着色器 为片元上色;
- 将片元投射到屏幕上的像素上。
更加形象一点的过程如下所示:
管线渲染-2
注意,其中 vertexShader 和 fragmentShader 是通过 GLSL 语言定义的,并直接运行在 GPU 上。
VertexShader
顶点着色器,主要用于确定顶点位置,由 GLSL 语言定义,对于每个顶点都会执行该程序。通常用法如下:
//通过 GLSL 定义 VertexShader
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 u_Matrix;"+
"void main() {" +
// "gl_Position = vPosition;" +
"gl_Position = u_Matrix * vPosition;" +
"gl_PointSize = 10.0;"+
"}";
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
...
//取出位置索引
aPositionLocation = GLES20.glGetAttribLocation(program,"vPosition") ;
//将位置索引和我们定义的数据源 vertexes 进行绑定,将 vertexes 中的每个顶点拿出来赋值到 vPosition ,并分别执行上面定义的 GLSL 程序
GLES20.glVertexAttribPointer(aPositionLocation,2,GLES20.GL_FLOAT,false,0,vertexes);
GLES20.glEnableVertexAttribArray(aPositionLocation);
}
FragmentShader
片元着色器,目的就是告诉 GPU 每个片段的最终颜色应该是什么。对于基于图元的每个片段,片段着色器都会被调用一次。因此,如果一个三角形被映射到 10000 个片段,片段着色器就会被调用 10000次。
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//取出颜色索引
aColorLocation = GLES20.glGetUniformLocation(program,"vColor") ;
}
@Override
public void onDrawFrame(GL10 gl) {
//给颜色赋值
GLES20.glUniform4f(aColorLocation, 0f,1,1, 1.0f);
//绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}
完整代码如下:
public class MyRenderer implements GLSurfaceView.Renderer {
private FloatBuffer vertexes ;
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"uniform mat4 u_Matrix;"+
"void main() {" +
"gl_Position = u_Matrix * vPosition;" +
"gl_PointSize = 10.0;"+
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";
private int aPositionLocation ;
private int aColorLocation ;
private int uMatrixLocation;
private final float[] projectionMatrix = new float[16];
private void createVertexes(){
float [] vertexesArray = new float[]{
0,1,
-1,-1,
1,-1
} ;
vertexes.clear();
vertexes.put(vertexesArray) ;
}
private void init(){
//创建本地内存,以便将我们定义的顶点放进去,供设备访问
//堆内存上的数据,GPU是无法直接访问的
vertexes = ByteBuffer.allocateDirect(6*4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer() ;
//创建 顶点 坐标
createVertexes();
}
public MyRenderer(){
init();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(1f,1f,0f,0f);
int vertexShader = ShaderHelper.compileVertexShader(vertexShaderCode) ;
int fragmentShader = ShaderHelper.compileFragmentShader(fragmentShaderCode) ;
int program = ShaderHelper.linkProgram(vertexShader, fragmentShader);
if (LoggerConfig.ON) {
ShaderHelper.validateProgram(program);
}
GLES20.glUseProgram(program);
aPositionLocation = GLES20.glGetAttribLocation(program,"vPosition") ;
aColorLocation = GLES20.glGetUniformLocation(program,"vColor") ;
uMatrixLocation = GLES20.glGetUniformLocation(program, "u_Matrix");
vertexes.position(0) ;
GLES20.glVertexAttribPointer(aPositionLocation,2,GLES20.GL_FLOAT,false,0,vertexes);
GLES20.glEnableVertexAttribArray(aPositionLocation);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
// 根据屏幕方向设置投影矩阵
float ratio= width > height ? (float)width / height : (float)height / width;
if (width > height) {
// 横屏
Matrix.orthoM(projectionMatrix, 0, -ratio, ratio, -1, 1, 0, 5);
} else {
Matrix.orthoM(projectionMatrix, 0, -1, 1, -ratio, ratio, 0, 5);
}
}
@Override
public void onDrawFrame(GL10 gl) {
// Clear the rendering surface.
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// Assign the matrix
GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0);
GLES20.glUniform4f(aColorLocation, 0f,1,1, 1.0f);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}
}
下面对以上代码块做几点说明:
- 在使用 OpenGL ES 的时候,我们一般会做以下处理:1. 编译顶点着色器;2. 编译出片元着色器;3. 创建程序;4.通过程序链接所有着色器;5. 使用程序,并通过程序,取出位置、颜色等索引,以便后期绘制。
- 上面代码块中,回调方法 onDrawFrame 的调用时机需要注意。默认情况下,会按照屏幕刷新的周期来调用该方法,即每秒执行 60 次。当然,我们可以通过在代码中设置以改变这种默认行为。
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
- 注意本地内存和虚拟机中堆内存的使用。后者是无法直接被设备访问的,后者可以,同时后者不受 GC 的影响。
- GLES20 中的所有方法,实际上都是本地方法,即通过 JNI 调用实现的,底层是由 C 语言实现。
小结
本节主要借用二维图形的绘制,讲解了 OpenGL ES 在 Android 应用中使用的相关概念。下面想讲述一下坐标变换。
参考链接:
https://developer.android.com/training/graphics/opengl/environment.html
http://www.cs.ucr.edu/~shinar/courses/cs130-spring-2012/schedule.html
https://www.zhihu.com/question/29163054
https://en.wikibooks.org/wiki/GLSL_Programming/OpenGL_ES_2.0_Pipeline
网友评论