要用OpenGl ES在应用程序中绘制图像,首先你要创建与之对应的视图容器。
当中最直接的方法就是使用GLSurfaceView和GLSurfaceView.Renderer接口。
GLSurfaceView是OpenGL所绘制的图形的视图容器,而GLSurfaceView.Renderer控制在视图上所绘制的图像。想获取更多关于这两个类的信息,请参考OpenGl ES开发指南。
GLSurfaceView只是把OpenGL ES图像组合到应用程序的其中一种方法,但对于绘制全屏或接近全屏的图形来说,它是一个很好的选择。开发者如果想把OpenGL ES图像组合成布局中的一小部分,可以查看TextureView。事实上,独立开发者也可以在SurfaceView上创建OpenGL ES视图,但这需要写更多额外的代码。
在Manifest声明OpenGL ES的使用
要在应用程序中使用 OpenGL ES 2.0 API,你必须在Manifest添加以下的声明:
- <uses-feature android:glEsVersion="0x00020000" android:required="true" />
如果你的应用程序中使用到了纹理压缩,你必须声明应用程序所支持的压缩格式,以便提醒不支持这些格式的设备不会尝试运行你的应用程序:
- <supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" />
- <supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />
想获取更多关于纹理压缩格式的信息,请参考OpenGl ES开发指南。
创建OpenGL ES的Activity
Android使用到OpenGL ES的应用程序和其他的应用程序的activities一样,都有一个用户界面。当中不同就是,在activity的布局中你使用了什么控件,在很多应用中,你可能会使用TextView,Button和ListView,在使用了OpenGL ES的应用程序,你还可以添加GLSurfaceView。
package com.example.opengldemo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class OpenGLES20Activity extends AppCompatActivity {
private GLSurfaceView glView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
glView = new MyGLSurfaceView(this);
setContentView(glView);
}
@Override
protected void onPause() {
super.onPause();
glView.onPause();
}
@Override
protected void onResume() {
super.onResume();
glView.onResume();
}
}
自定义GLSurfaceView:
package com.example.opengldemo;
import android.content.Context;
import android.opengl.GLSurfaceView;
/**
* GLSurfaceView 是一个可以绘制OpenGLES图像的专门视图,它本身没有处理功能,
*
* 所绘制的图像都是由你所设置的GLSurfaceView.Render来控制的。
*
*/
public class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRenderer renderer;
public MyGLSurfaceView(Context context){
super(context);
// 声明你使用的是OpenGlES2.0的API
setEGLContextClientVersion(2);
renderer = new MyGLRenderer();
// Set the Renderer for drawing on the GLSurfaceView
setRenderer(renderer);
}
}
GLSurfaceView.Renderer渲染器介绍
GLSurfaceView是一个可以绘制OpenGL ES图像的专门视图,它本身没有处理功能,所绘制的图像都是由你所设置的GLSurfaceView.Renderer来控制的。Render就是整个程序的重点:
package com.example.opengldemo;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
public class MyGLRenderer implements GLSurfaceView.Renderer {
private Triangle mTriangle;
/**
* shader语言跟C语言很像,它有一个主函数,也叫void main(){}。
* gl_Position是一个内置变量,用于指定顶点,它是一个点,三维空间的点,所以用一个四维向量来赋值。vec4是四维向量的类型,vec4()是它的构造方法。等等,三维空间,
* 不是(x, y, z)三个吗?咋用vec4呢?四维是叫做齐次坐标,它的几何意义仍是三维,先了解这么多,记得对于2D的话,第四位永远传1.0就可以了。这里,是指定原点
* (0, 0, 0)作为顶点,就是说想在原点(正中心)位置画一个点。gl_PointSize是另外一个内置变量,用于指定点的大小。这个shader就是想在原点画一个尺寸为20的点
*
*/
private String VERTEX_SHADER =
"void main() {\n" +
"gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" +
"gl_PointSize = 200.0;\n" +
"}\n";
private String FRAGMENT_SHADER =
"void main() {\n" +
"gl_FragColor = vec4(1., 0., 0.0, 1.0);\n" +
"}\n";
private int mGLProgram;
//设置视图的OpenGL ES环境,只需调用一次
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Set the background frame color
GLES20.glClearColor(1.0f, 1.0f, 0.0f, 1.0f); //设置清空屏幕用的颜色,设置背景底色
/**
* shader
* GL ES 2.0与1.0版本最大的区别在于,把渲染相关的操作用一个专门的叫作着色语言的程序来表达,全名叫作OpenGL ES Shading language,它是一个编程语言,与C语言非常类似,
* 能够直接操作矩阵和向量,运行在GPU之上专门用于图形渲染。它又分为两种,一个叫做顶点着色器(vertex shader),另一个叫做片元着色器(fragment shader)。
*前者用来指定几何形状的顶点;后者用于指定每个顶点的着色。每个GL程序必须要有一个vertex shader和一个fragment shader,且它们是相互对应的。(相互对应,
* 意思是vertex shader必须要有一个fragment shader,反之亦然,但并不一定是一一对应)。当然,也是可以复用的,比如同一个vertex shader,可能会多个fragment shader
* 来表达不同的着色方案。
*
*/
**
*
* 创建一个着色器分三步:
*
* 1) 创建Shader对象
*
* 2) 装载Shader源码
*
* 3) 编译Shader
*/
/**
* 1)glCreateShader
* 它创建一个空的shader对象,它用于维护用来定义shader的源码字符串。支持以下两种shader:
* (1) GL_VERTEX_SHADER: 它运行在可编程的“顶点处理器”上,用于代替固定功能的顶点处理;
* (2) GL_FRAGMENT_SHADER: 它运行在可编程的“片断处理器”上,用于代替固定功能的片段处理;
*/
int vertextShaderId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
/**
* 2)glShaderSource装载源码:
* shader对象中原来的源码全部被新的源码所代替。
*/
GLES20.glShaderSource(vertextShaderId,VERTEX_SHADER);
/**
* 3)glCompileShader
* 编译存储在shader对象中的源码字符串,编译结果被当作shader对象状态的一部分被保存起来,可通过
* glGetShaderiv函数获取编译状态。
*/
GLES20.glCompileShader(vertextShaderId);
// 创建一个片着色器,三个步骤介绍同上
int fragmentShaderId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
GLES20.glShaderSource(fragmentShaderId,FRAGMENT_SHADER);
GLES20.glCompileShader(fragmentShaderId);
//建立一个空的program对象,shader对象可以被连接到program对像
mGLProgram = GLES20.glCreateProgram();
// 把vertex shader添加到program
GLES20.glAttachShader(mGLProgram,vertextShaderId);
// 把fragment shader添加到program
GLES20.glAttachShader(mGLProgram,fragmentShaderId);
// 做链接,可以理解为把两种shader进行融合,做好投入使用的最后准备工作
GLES20.glLinkProgram(mGLProgram);
}
// 每次重绘都会重新调用一次
public void onDrawFrame(GL10 unused) {
// //擦除屏幕上的所有颜色,并用 glClearColor 中的颜色填充背景,因为我们要开始新一帧的绘制了,所以先清理,以免有脏数据。
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 告诉OpenGL,使用我们在onSurfaceCreated里面准备好了的shader program来渲染
GLES20.glUseProgram(mGLProgram);
// 开始渲染,发送渲染点的指令, 第二个参数是offset,第三个参数是点的个数。目前只有一个点,所以是1。
GLES20.glDrawArrays(GLES20.GL_POINTS,0,1);
}
//视图的几何发现变化时调用,例如,设备的屏幕的方向发生变化时才会调用
public void onSurfaceChanged(GL10 unused, int width, int height) {
//设置了视口尺寸,告诉 OpenGL 可以用来渲染的 surface 的大小。
//(0, 0)是左上角,然后是width和heigh
GLES20.glViewport(0, 0, width, height);
}
}
在使用OpenGL ES 2.0时,你必须在你的GLSurfaceView构造器中添加多一句代码,以声明你是使用OpenGL ES 2.0的API:
// Create an OpenGL ES 2.0 context
setEGLContextClientVersion(2);
备注:如果你使用的是OpenGL ES 2.0的API,请确认你已经在应用程序的manifest进行了声明。
现在我们分析上面的Render渲染器。渲染器中提供了三个方法给Android系统调用,以控制什么可以,以及怎样绘制在GLSurfaceView上。它们分别是:
* onSurfaceCreated()) - 设置视图的OpenGL ES环境,只需调用一次
* onDrawFrame()) - 重新绘制每个视图时调用
* onSurfaceChanged()) - 视图的几何发现变化时调用,例如,设备的屏幕的方向发生变化
最后一个概念:着色器 shader
GL ES 2.0与1.0版本最大的区别在于,把渲染相关的操作用一个专门的叫作着色语言的程序来表达,全名叫作OpenGL ESShading language,它是一个编程语言,与C语言非常类似,能够直接操作矩阵和向量,运行在GPU之上专门用于图形渲染。它又分为两种,一个叫做顶点着色器(vertex shader),另一个叫做片元着色器(fragment shader)。前者用来指定几何形状的顶点;后者用于指定每个顶点的着色。每个GL程序必须要有一个vertex shader和一个fragment shader,且它们是相互对应的。(相互对应,意思是vertex shader必须要有一个fragment shader,反之亦然,但并不一定是一一对应)。当然,也是可以复用的,比如同一个vertex shader,可能会多个fragment shader来表达不同的着色方案。 下面就是一段着色器片段:
//点着色器,定义点的位置和大小
private String VERTEX_SHADER =
"void main() {\n" +
"gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" +
"gl_PointSize = 200.0;\n" +
"}\n";
//片着色器,定义由点组成的点或线或面的属性,比如填充颜色
private String FRAGMENT_SHADER =
"void main() {\n" +
"gl_FragColor = vec4(1.0, 0., 0.0, 1.0);\n" +
"}\n";
shader语言跟C语言很像,它有一个主函数,也叫void main(){}
gl_Position是一个内置变量,用于指定顶点,它是一个点,三维空间的点,所以用一个四维向量来赋值。
vec4是四维向量的类型,vec4()是它的构造方法。等等,三维空间,不是(x, y, z)三个吗?咋用vec4呢?四维是叫做齐次坐标,它的几何意义仍是三维,先了解这么多,记得对于2D的话,第四位永远传1.0就可以了。
这里,是指定原点(0, 0, 0)作为顶点,就是说想在原点(正中心)位置画一个点。由于这里只定义了一个点。并且指定填充颜色为红色,所以它的运行效果就是在手机(0,0,0)正中心画一个红色的大小为200像素的点,效果如图:

好了,我们的第一堂实践课就到这里了。