Android音視頻(一) OpenGL ES2.0 繪製圖片紋理

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);
        //設置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);

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章