OpenGL ES是OpenGL的一个子集,它针对移动端或嵌入式系统做了部分精简,而Android系统中集成了OpenGL ES,方便我们通过其接口充分使用GPU的计算和渲染能力。
OpenGL ES2.0是基于可编程管线设计。相对OpenGL ES 1.x,OpenGL ES 2.0进行了大变革,更具灵活性,功能也更强大,并且渲染效率更高,效果更好。目前Android对OpenGL ES的支持如下:
- OpenGL ES 1.0 和 1.1 能够被Android 1.0及以上版本支持
- OpenGL ES 2.0 能够被Android 2.2及更高版本支持
- OpenGL ES 3.0 能够被Android 4.3及更高版本支持
- OpenGL ES 3.1 能够被Android 5.0及以上版本支持
相比起来学习OpenGL ES选择2.0版本是一个相对最佳的选择,可以兼容4.0以上的手机设备,并且OpenGL ES 3.x都向下兼容OpenGL ES 2.0。本系列以OPENGL ES2.0演示和开发。
这里主要关注OpenGL ES的这两方面能力:
- 摄像头预览效果处理。
- 视频处理。
Android框架里面两个基本的类来方便使用OpenGL ES API创建和操作图形: GLSurfaceView
和 GLSurfaceView.Renderer
。GLSurfaceView
是管理OpenGL surface的一个特殊的View,它可以帮助我们把OpenGL的surface渲染到Android的View上,并且封装了很多创建OpenGL环境所需要的配置,使我们能够更方便地使用OpenGL。
GLSurfaceView.Renderer
接口定义了在GLSurfaceView中绘制图形所需的方法。通常情况下我们使用GLSurfaceView.setRenderer()
方法将此接口实现设置进GLSurfaceView
中。此接口方法主要为:
- onSurfaceCreated():创建GLSurfaceView时,系统调用该方法。使用此方法执行只需要执行一次的操作,如设置OpenGL环境参数或初始化OpenGL图形对象。
- onDrawFrame():系统在每次重绘GLSurfaceView时调用这个方法。使用此方法作为绘制图形时的主要方法。
- onSurfaceChanged():当GLSurfaceView的大小或设备屏幕方向发生变化时,系统调用此方法。例如:设备从纵向变为横向时,系统调用此方法。我们应该使用此方法来响应GLSurfaceView容器的改变。
1. 编写着色器(顶点着色器和片元着色器)
在AndroidManifest.xml文件中设置使用的OpenGL ES的版本:
"0x00020000" android:required="true" />
复制代码
一般来说,我们如下创建GLSurfaceView
,设置 GLSurfaceView.Renderer
即可。接下来我们将重点放在 GLSurfaceView.Renderer
的编写上面。
public class ZkGLSurfaceView extends GLSurfaceView {
public ZkGLSurfaceView(Context context) {
this(context,null);
}
public ZkGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
//使用OpenGL ES 2.0
setEGLContextClientVersion(2);
//设置Renderer
setRenderer(new MyRenderer(context));
//设置刷新模式
setRenderMode(RENDERMODE_WHEN_DIRTY);
}
}
复制代码
定点着色器
attribute vec4 vPosition;
attribute vec2 vCoordinate;
varying vec2 aCoordinate;
void main() {
gl_Position = vPosition;
aCoordinate = vCoordinate;
}
复制代码
片元着色器
precision mediump float;
uniform sampler2D vTexture;
varying vec2 aCoordinate;
void main() {
gl_FragColor = texture2D(vTexture,aCoordinate);
}
复制代码
gl_Position
和gl_FragColor
都是Shader的内置变量,分别为定点位置和片元颜色。
attribute 一般用于各个顶点各不相同的量。如顶点颜色、坐标等
varying 用于vertex和fragment之间传递值,一般用于顶点着色器传递到片元着色器的量
uniform 一般用于对于3D物体中所有顶点都相同的量。比如光源位置,统一变换矩阵等
2.设置顶点、纹理坐标
//顶点坐标
private float[] vertex = {
-1.0f,1.0f, //左上角
-1.0f,-1.0f, //左下角
1.0f,1.0f, //右上角
1.0f,-1.0f //右下角
};
private final float[] sCoord={
0f, 0f, //左上角
0f, 1f, //左下角
1f, 0f, //右上角
1f, 1f //右下角
};
//申请底层空间 将坐标数据转换为FloatBuffer,用以传入给OpenGL ES程序
mVertexBuffer = ByteBuffer.allocateDirect(vertex.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer().put(vertex);
mVertexBuffer.position(0);
mFragmentBuffer = ByteBuffer.allocateDirect(sCoord.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer().put(sCoord);
mFragmentBuffer.position(0);
复制代码
顶点坐标和纹理坐标的环绕方向必须一致。
3.加载着色器
int vertex_shader = loadShader(GLES20.GL_VERTEX_SHADER,VERTEX_SHADER);
int fragment_shader = loadShader(GLES20.GL_FRAGMENT_SHADER,FRAGMENT_SHADER);
private int loadShader(int glVertexShader, String vertexShader) {
//创建shader(着色器:顶点或片元)
int glCreateShader = GLES20.glCreateShader(glVertexShader);
//加载shader源码并编译shader
GLES20.glShaderSource(glCreateShader, vertexShader);
int[] compiled = new int[1];
//检查是否编译成功
GLES20.glGetShaderiv(glCreateShader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] != GLES20.GL_TRUE){
GLES20.glDeleteShader(glCreateShader);
return -1;
}
return glCreateShader;
}
复制代码
4.创建Program
//创建渲染程序
mProgram = GLES20.glCreateProgram();
//将着色器程序添加到渲染程序中
GLES20.glAttachShader(mProgram, vertex_shader);
GLES20.glAttachShader(mProgram, fragment_shader);
//链接源程序
GLES20.glLinkProgram(mProgram);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE){
String info = GLES20.glGetProgramInfoLog(mProgram);
GLES20.glDeleteProgram(mProgram);
throw new RuntimeException("Could not link program: " + info);
}
复制代码
5.创建纹理
private int loadTexture(int resId){
int[] textures = new int[1];
//创建和绑定纹理
GLES20.glGenTextures(1,textures,0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
//激活第0个纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glUniform1i(mVTexture, 0);
//设置环绕和过滤方式
//环绕(超出纹理坐标范围):(s==x t==y GL_REPEAT 重复)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
//过滤(纹理像素映射到坐标点):(缩小、放大:GL_LINEAR线性)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), resId);
//设置图片
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
bitmap = null;
//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return textures[0];
}
复制代码
6.渲染图片
@Override
public void onDrawFrame() {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(1.0f, 0, 0, 1f);
//使用源程序
GLES20.glUseProgram(mProgram);
//绑定绘制纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureid);
//使顶点属性数组有效
GLES20.glEnableVertexAttribArray(mVPosition);
//为顶点属性赋值
GLES20.glVertexAttribPointer(mVPosition, 2, GLES20.GL_FLOAT, false, 8, mVertexBuffer);
GLES20.glEnableVertexAttribArray(mVCoordinate);
GLES20.glVertexAttribPointer(mVCoordinate, 2, GLES20.GL_FLOAT, false, 8, mFragmentBuffer);
//绘制图形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}
复制代码
完整代码:
public class MyRenderer implements GLSurfaceView.Renderer{
private static final String VERTEX_SHADER = "attribute vec4 vPosition;\n"
+ "attribute vec2 vCoordinate;\n"
+ "varying vec2 aCoordinate;\n"
+ "void main() {\n"
+ "gl_Position = vPosition;\n"
+ "aCoordinate = vCoordinate;\n"
+ "}";
private static final String FRAGMENT_SHADER = "precision mediump float;\n"
+ "uniform sampler2D vTexture;\n"
+ "varying vec2 aCoordinate;\n"
+ "void main() {\n"
+ "gl_FragColor = texture2D(vTexture,aCoordinate);\n"
+ "}";
private float[] vertex = {
-1.0f,1.0f, //左上角
-1.0f,-1.0f, //左下角
1.0f,1.0f, //右上角
1.0f,-1.0f //右下角
};
private final float[] sCoord={
0f, 0f, //左上角
0f, 1f, //左下角
1f, 0f, //右上角
1f, 1f //右下角
};
private FloatBuffer mVertexBuffer;
private FloatBuffer mFragmentBuffer;
private int mProgram;
private int mVPosition;
private int mVCoordinate;
private int mVTexture;
private Context mContext;
private int mTextureid;
public MyRenderer(Context context){
mContext = context;
mVertexBuffer = ByteBuffer.allocateDirect(vertex.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer().put(vertex);
mVertexBuffer.position(0);
mFragmentBuffer = ByteBuffer.allocateDirect(sCoord.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer().put(sCoord);
mFragmentBuffer.position(0);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
int vertex_shader = loadShader(GLES20.GL_VERTEX_SHADER,VERTEX_SHADER);
int fragment_shader = loadShader(GLES20.GL_FRAGMENT_SHADER,FRAGMENT_SHADER);
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vertex_shader);
GLES20.glAttachShader(mProgram, fragment_shader);
GLES20.glLinkProgram(mProgram);
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE){
String info = GLES20.glGetProgramInfoLog(mProgram);
GLES20.glDeleteProgram(mProgram);
throw new RuntimeException("Could not link program: " + info);
}
mVPosition = GLES20.glGetAttribLocation(mProgram, "vPosition");
mVCoordinate = GLES20.glGetAttribLocation(mProgram, "vCoordinate");
mVTexture = GLES20.glGetUniformLocation(mProgram, "vTexture");
mTextureid = loadTexture(R.drawable.fengj);
}
private int loadTexture(int resId){
int[] textures = new int[1];
//创建和绑定纹理
GLES20.glGenTextures(1,textures,0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
//激活第0个纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glUniform1i(mVTexture, 0);
//设置环绕和过滤方式
//环绕(超出纹理坐标范围):(s==x t==y GL_REPEAT 重复)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
//过滤(纹理像素映射到坐标点):(缩小、放大:GL_LINEAR线性)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), resId);
//设置图片
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
bitmap = null;
//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
return textures[0];
}
private int loadShader(int glVertexShader, String vertexShader) {
int glCreateShader = GLES20.glCreateShader(glVertexShader);
GLES20.glShaderSource(glCreateShader, vertexShader);
GLES20.glCompileShader(glCreateShader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(glCreateShader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] != GLES20.GL_TRUE){
GLES20.glDeleteShader(glCreateShader);
return -1;
}
return glCreateShader;
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(1.0f, 0, 0, 1f);
//使用源程序
GLES20.glUseProgram(mProgram);
//绑定绘制纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureid);
//使顶点属性数组有效
GLES20.glEnableVertexAttribArray(mVPosition);
//为顶点属性赋值
GLES20.glVertexAttribPointer(mVPosition, 2, GLES20.GL_FLOAT, false, 8, mVertexBuffer);
GLES20.glEnableVertexAttribArray(mVCoordinate);
GLES20.glVertexAttribPointer(mVCoordinate, 2, GLES20.GL_FLOAT, false, 8, mFragmentBuffer);
//绘制图形
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
//解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}
}
复制代码