Android音视频(一) OpenGL ES2.0 绘制图片纹理

1,647 阅读5分钟

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创建和操作图形: GLSurfaceViewGLSurfaceView.RendererGLSurfaceView是管理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的版本:

<uses-feature android:glEsVersion="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_Positiongl_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);

    }
}