天天看點

Android GLSurfaceView用法解析OpenGL和OpenGLES簡介

OpenGL和OpenGLES簡介

      這裡先簡單介紹下OpenGL和OpenGLES。OpenGL(Open Graphics Library)意為開放圖形庫,是一個跨平台的圖形API,用于指定3D圖形處理硬體中的軟硬體程式設計接口。OpenGL一般用于圖形工作站,PC端使用。由于性能和可移植性等各方面原因,在移動端使用起來相對比較麻煩。為此,Khronos公司就為OpenGL提供一個子集,OpenGL ES(OpenGL for Embedded System)。OpenGL ES是免費的跨平台且功能完善的2D/3D圖形庫接口API,是OpenGL的一個子集。

        在Android端使用OpenGL ES的過程中,GLSurfaceView就扮演者一個重要的角色。本篇博文将結合幾個簡單的案例分别介紹GLSurfaceView的基本使用以及如何使用GLSurfaceView.Renderer所提供的接口将圖形繪制到SurfaceView中

GLSurfaceView的特點

  • GLSurfaceView繼承SurfaceView,并實作了SurfaceHolder.Callback2接口。既然繼承于SurfaceView,就擁有SurfaceView的特性。如能在View的基礎上建立獨立的Surface,擁有SurfaceHolder來管理Surface,并且可以在子線程中進行View的渲染操作。通過SurfaceHolder得到Canvas,然後在單獨的線程中利用Canvas繪制内容,然後更新到Surface。
  • GLSurfaceView在SurfaceView的基礎上實作GLThread(EGLContext建立GL環境所線上程),可通過OpenGL進行繪制,可在SurfaceView所提供的Surface進行繪制

GLSurfaceView的使用步驟

1.在使用OpenGL ES前,需要在AndroidManifest.xml中設定OpenGL ES的版本,這裡我們使用的是OpenGL ES2.0,是以可進行如下聲明。

2.在Activity的onCreate方法中對GLSurfaceView進行初始化,設定相關配置資訊

glSurfaceView = (GLSurfaceView) findViewById(R.id.glSurfaceView);
        //GLContext設定OpenGLES2.0
        glSurfaceView.setEGLContextClientVersion();
        // 在setRenderer之前,可以調用以下方法進行EGL設定
//        glSurfaceView.setEGLConfigChooser(true);//顔色,深度,模闆等等設定
//        glSurfaceView.setEGLWindowSurfaceFactory(new GLSurfaceView.EGLWindowSurfaceFactory() {   //視窗設定
//            @Override
//            public EGLSurface createWindowSurface(EGL10 egl10, EGLDisplay eglDisplay, EGLConfig eglConfig, Object o) {
//                return null;
//            }
//
//            @Override
//            public void destroySurface(EGL10 egl10, EGLDisplay eglDisplay, EGLSurface eglSurface) {
//
//            }
//        });
        glSurfaceView.setRenderer(new TriangleRender());
        /*渲染方式,RENDERMODE_WHEN_DIRTY表示被動渲染,隻有在調用requestRender或者onResume等方法時才會進行渲染。RENDERMODE_CONTINUOUSLY表示持續渲染*/
        glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
           
  1. 在Activity或者Fragment的onResume方法和onPause方法中,分别調用GLSurfaceView的onResume和onPause方法。
@Override
    protected void onResume() {
        super.onResume();
        glSurfaceView.onResume();
    }
    @Override
    protected void onPause() {
        super.onPause();
        glSurfaceView.onPause();
    }
           
  1. 編寫渲染器定義,實體類實作GLSurfaceView.Renderer接口,并實作onSurfaceCreated ,onDrawFrame,onSurfaceChanged方法,GLSurfaceView的渲染工作主要由GLSurfaceView.Renderer負責渲染。
public class BackgroundRender extends BaseRenderer implements GLSurfaceView.Renderer {

    private String TAG = BackgroundRender.class.getSimpleName();

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        //surface被建立後需要做的處理
        //Set the background frame color
        GLES20.glClearColor(f,f,f,f);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        // 渲染視窗大小發生改變或者螢幕方法發生變化時候回調
        GLES20.glViewport(,,width,height);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        //執行渲染工作
        //Redraw background color
        GLES20.glClearColor(GLES20.GL_COLOR_BUFFER_BIT,f,f,f);
    }
}
           

效果如下:給GLSurfaceView渲染一層黑色背景

Android GLSurfaceView用法解析OpenGL和OpenGLES簡介

用OpenGL在GLSurfaceView上繪制幾何圖形

繪制幾何圖形,需要将頂點坐标轉為ByteBuffer,這樣OpenGL才能進行圖形處理。繪制三角形和正方形都需要加載頂點着色器和片元着色器的相關配置,關于着色器後面的博文會有相應介紹。這裡我們定義BaseRenderer,在裡面定義loadShader

方法用于建立,加載和編譯對應的着色器配置資訊。繪制其他形狀的渲染器都繼承BaseRenderer。

三角形圖形繪制

public class TriangleRender extends BaseRenderer implements GLSurfaceView.Renderer {

    private int mProgram;
    private FloatBuffer vertexBuffer;

    private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "void main() {" +
                    "  gl_Position = vPosition;" +
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";

    static float triangleCoords[] = {
            f,  f, f, // top
            -f, -f, f, // bottom left
            f, -f, f  // bottom right
    };

    //設定顔色,依次為紅綠藍和透明通道
    float color[] = { f, f, f, f };
    static final int COORDS_PER_VERTEX = ;

    private int mPositionHandle;
    private int mColorHandle;

    //頂點個數
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    //頂點之間的偏移量
    private final int vertexStride = COORDS_PER_VERTEX * ; // 每個頂點四個位元組


    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        //将背景設定為灰色
        GLES20.glClearColor(f,f,f,f);
        //申請底層空間
        ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * );
        bb.order(ByteOrder.nativeOrder());
        //将坐标資料轉換為FloatBuffer,用以傳入OpenGL ES程式
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(triangleCoords);
        vertexBuffer.position();

        int vertexShader =  loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);

        //建立一個空的OpenGLES程式
        mProgram = GLES20.glCreateProgram();
        //将頂點着色器加入到程式
        GLES20.glAttachShader(mProgram,vertexShader);
        //将片元着色器加入到程式中
        GLES20.glAttachShader(mProgram,fragmentShader);
        //連接配接到着色器程式
        GLES20.glLinkProgram(mProgram);
    }





    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        GLES20.glViewport(,,width,height);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        //将程式加入到OpenGLES2.0環境
        GLES20.glUseProgram(mProgram);

        //擷取頂點着色器的vPosition成員句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        //啟用三角形頂點的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //準備三角形的坐标資料
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);
        //擷取片元着色器的vColor成員的句柄
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        //設定繪制三角形的顔色
        GLES20.glUniform4fv(mColorHandle, , color, );
        //繪制三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, , vertexCount);
        //禁止頂點數組的句柄
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}
           

這是定義TriangleRender繼承BaseRenderer并實作GLSurfaceView.Renderer接口在GLSurfaceView上進行繪制三角形操作,隻需将GLSurfaceView的Renderer設定為TriangleRender即可

glSurfaceView.setRenderer(new TriangleRender());
           

運作效果如下

Android GLSurfaceView用法解析OpenGL和OpenGLES簡介

繪制正方形

public class Square extends BaseRenderer implements GLSurfaceView.Renderer {

    private FloatBuffer vertexBuffer;
    private ShortBuffer indexBuffer;
    private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "uniform mat4 vMatrix;"+
                    "void main() {" +
                    "  gl_Position = vMatrix*vPosition;" +
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";

    private int mProgram;

    static final int COORDS_PER_VERTEX = ;
    static float triangleCoords[] = {
            -f,  f, f, // top left
            -f, -f, f, // bottom left
            f, -f, f, // bottom right
            f,  f, f  // top right
    };

    static short index[]={
            ,,,,,
    };

    private int mPositionHandle;
    private int mColorHandle;

    private float[] mViewMatrix=new float[];
    private float[] mProjectMatrix=new float[];
    private float[] mMVPMatrix=new float[];

    //頂點個數
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    //頂點之間的偏移量
    private final int vertexStride = COORDS_PER_VERTEX * ; // 每個頂點四個位元組

    private int mMatrixHandler;

    //設定顔色,依次為紅綠藍和透明通道
    float color[] = { f, f, f, f };


    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        ByteBuffer bb = ByteBuffer.allocateDirect(
                triangleCoords.length * );
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(triangleCoords);
        vertexBuffer.position();

        ByteBuffer cc= ByteBuffer.allocateDirect(index.length*);
        cc.order(ByteOrder.nativeOrder());
        indexBuffer=cc.asShortBuffer();
        indexBuffer.put(index);
        indexBuffer.position();

        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        //建立一個空的OpenGLES程式
        mProgram = GLES20.glCreateProgram();
        //将頂點着色器加入到程式
        GLES20.glAttachShader(mProgram, vertexShader);
        //将片元着色器加入到程式中
        GLES20.glAttachShader(mProgram, fragmentShader);
        //連接配接到着色器程式
        GLES20.glLinkProgram(mProgram);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
//計算寬高比
        float ratio=(float)width/height;
        //設定透視投影
        Matrix.frustumM(mProjectMatrix, , -ratio, ratio, -, , , );
        //設定相機位置
        Matrix.setLookAtM(mViewMatrix, , , , f, f, f, f, f, f, f);
        //計算變換矩陣
        Matrix.multiplyMM(mMVPMatrix,,mProjectMatrix,,mViewMatrix,);
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        //将程式加入到OpenGLES2.0環境
        GLES20.glUseProgram(mProgram);
        //擷取變換矩陣vMatrix成員句柄
        mMatrixHandler= GLES20.glGetUniformLocation(mProgram,"vMatrix");
        //指定vMatrix的值
        GLES20.glUniformMatrix4fv(mMatrixHandler,,false,mMVPMatrix,);
        //擷取頂點着色器的vPosition成員句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        //啟用三角形頂點的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //準備三角形的坐标資料
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);
        //擷取片元着色器的vColor成員的句柄
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
        //設定繪制三角形的顔色
        GLES20.glUniform4fv(mColorHandle, , color, );
        //繪制三角形
//        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
        //索引法繪制正方形
        GLES20.glDrawElements(GLES20.GL_TRIANGLES,index.length, GLES20.GL_UNSIGNED_SHORT,indexBuffer);
        //禁止頂點數組的句柄
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}
           

将Square作為渲染器設定到GLSurfaceView中

glSurfaceView.setRenderer(new Square());
           

運作效果如下

Android GLSurfaceView用法解析OpenGL和OpenGLES簡介

三角形圖形繪制

一個顔色是不是太單調了些呢?接下來我們繪制一個彩色三角形。基于上面的代碼,我們隻需做如下調整

1.修改着色器代碼

2.将顔色值修改為float數組并轉為floatBuffer

3.将擷取的floatBuffer傳遞給頂點着色器

修改着色器代碼如下:

private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "uniform mat4 vMatrix;"+
                    "varying  vec4 vColor;"+
                    "attribute vec4 aColor;"+
                    "void main() {" +
                    "  gl_Position = vMatrix*vPosition;" +
                    "  vColor=aColor;"+
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "varying vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";
           

接下來我們進行資料轉換:

//設定顔色
    float color[] = {
            , , ,  ,
            , , , ,
            , , , 
    };
    ByteBuffer bb = ByteBuffer.allocateDirect(
                triangleCoords.length * );
    bb.order(ByteOrder.nativeOrder());

    vertexBuffer = bb.asFloatBuffer();
    vertexBuffer.put(triangleCoords);
    vertexBuffer.position();
           

最後我們需要擷取着色器的句柄并設定着色器的顔色

@Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT| GLES20.GL_DEPTH_BUFFER_BIT);
        //将程式加入到OpenGLES2.0環境
        GLES20.glUseProgram(mProgram);
        //擷取變換矩陣vMatrix成員句柄
        mMatrixHandler= GLES20.glGetUniformLocation(mProgram,"vMatrix");
        //指定vMatrix的值
        GLES20.glUniformMatrix4fv(mMatrixHandler,,false,mMVPMatrix,);
        //擷取頂點着色器的vPosition成員句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        //啟用三角形頂點的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //準備三角形的坐标資料
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);
        //擷取片元着色器的vColor成員的句柄
        mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
        //設定繪制三角形的顔色
        GLES20.glEnableVertexAttribArray(mColorHandle);
        GLES20.glVertexAttribPointer(mColorHandle,,
                GLES20.GL_FLOAT,false,
                ,colorBuffer);
        //繪制三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, , vertexCount);
        //禁止頂點數組的句柄
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
           

運作效果如下:

Android GLSurfaceView用法解析OpenGL和OpenGLES簡介

完整Demo位址:GitHub位址 喜歡給個Star 謝謝

繼續閱讀