Android GlSurfaceView总结及代码示例讲解

##.GlSurfaceView 简介:
GlSurfaceView继承自SurfaceView,额外提供了一些能力:
           1.支持用OpenGl对图像渲染后显示到Surface上,
           2.提供了渲染器类Render,渲染器中有Surface的生命周期回调接口,以及每一帧图像的渲染接口。
           3.提供了单独的GlThrad线程,设置渲染器后,该线程会启动,初始化EGL环境,并在线程中根据状态变化做Render中各个API接口的回调。
##.OpenGl简单:
    OpenGl是一个图形处理的标准接口库,它是跨平台的。
    手机由于性能相对较弱,难以支持OpenGl的全部功能,所以Android中提供OpenGl的子集OpenGl ES。
    Android中为图像提供OpenGl渲染能力并显示的控件是GlSurfaceView。
    在GlSurfaceView中GLThread会创建EGL环境,EGL是OpenGl和Android本地窗口系统之间的接口,OpenGl通过EGL来实现界面绘制。
    而GlSurfaceView会自动将EGL环境配置好,将OpenG渲染l处理后的图像绘制到界面上。
##.EGL简介:
   OpenGl是一套跨平台的接口,它与各个平台本地窗口系统之间的交互,是借助于一个中间控制层,这个中间控制层就是EGL。 EGL也有自己的一套标准API,由各个平台的系统来完成其具体实现。  
   EGL是OpenGL和本地窗口体系进行联系的桥梁,负责管理OpenGL的运行状态、渲染图像到本地窗口或缓冲区等功能。
   在Android系统中也是一样,OpenGL的每一步处理,都需要依赖于EGL提供的这些相关功能支持,所以必须先创建EGL环境,才能正常进行OpenGL处理。
   不过Android中GLSurfaceView会自己在GLThread中完成EGL环境的初始化,使用GLSurfaceView时,开发者并不需要自己来初始化EGL环境。
#.内部机制分析:
GLThread:调用SurfaceView的setRender()后,SurfaceView内部会创建GLThread线程,并在线程内部 创建EGL环境。
     GLThread的核心代码在 guardedRun()中,里面是一个while(true){...}死循环,进行EGL环境的管理并根据条件判断调用 Render的相关方法, 如Render的 onSurfaceCreated(), onSurfaceChanged()和onDrawFrame()方法等。因为OpenGl ES的绘制依赖EGL环境,所以必须在这个线程中进行。
EglHelper:负责为GLThread创建EGL环境。
GLSurfaceView:继承了SurfaceView的所有特性,负责提供Surface并管理Surface的状态。
##.重要API
&&.设置渲染器
TestRender render = new TestRender();
//为GlSurfaceView设置渲染器Render
//这一步很关键,当设置渲染器后,会触发GlThread线程启动,然后创建EGL环境,
// 并在线程中根据条件判断,来对应调用Render的各个接口
mGlSurfaceView.setRenderer(render);
&&.设置渲染模式
/*设置GLThread的渲染方式,该值会影响GLThread内部while(true){}死循环内部的条件判断
1.Render.RENDERMODE_WHEN_DIRTY:表示被动渲染,需要手动调用相应方法来触发渲染。
    设置该值,GLThread在死循环死循环中不会自动调用Render.onDrawFrame()
    只有在调用requestRender()或者onResume()等方法后才会改变死循环中的判断条件,调用一次Render.onDrawFrame()
2.Render.RENDERMODE_CONTINUOUSLY:表示持续渲染,该值为GLThread的默认渲染模式
    设置该值时,GLThread在死循环中会不断的自动调用Render.onDrawFrame()。因此会比较消耗手机性能。*/
//当需要固定的刷新频率时,例如30帧/s,应该用Render.RENDERMODE_WHEN_DIRTY模式,然后手动控制刷新。
//注意:必须在设置Render之后才能设置,否则会报错
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
&&.当调用requestRender()时,会手动在GLThread中触发一次Render.onDrawFrame()调用,进行一次绘制操作。
&&.由于操作OpenGL环境只能通过GL线程,所以GLSurfaceView为我们提供了queueEvent(Runnable r)这个方法,GLThread在内部死循环while(true){…}中,会调用这个传入的Runnable的run()方法。
   
&&.onPause()/onResume()
这两个方法实际回去调用GLThread的onPause()/onResume()。
onPause()会停止GLThread中的绘制,不再回调Render中的回到接口。
onResume()会恢复GLThread中的绘制,恢复回调Render中的回到接口。
(看源码,onPause()时GLThread会销毁EGL环境,onResume()时又会恢复EGL环境。)
#.代码示例与讲解(见代码注释)
public class TestGlSurfaceView extends GLSurfaceView {
    public TestGlSurfaceView(Context context) {
        this(context, null);
    }

    public TestGlSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 使用OpenGL ES 2.0
        setEGLContextClientVersion(2);
    }

    @Override
    public void setRenderer(Renderer renderer) {
        super.setRenderer(renderer);
        /*设置GLThread的渲染方式,该值会影响GLThread内部while(true){}死循环内部的条件判断
        1.Render.RENDERMODE_WHEN_DIRTY:表示被动渲染,需要手动调用相应方法来触发渲染。
            设置该值,GLThread在死循环死循环中不会自动调用Render.onDrawFrame()
            只有在调用requestRender()或者onResume()等方法后才会改变死循环中的判断条件,调用一次Render.onDrawFrame()
        2.Render.RENDERMODE_CONTINUOUSLY:表示持续渲染,该值为GLThread的默认渲染模式
            设置该值时,GLThread在死循环中会不断的自动调用Render.onDrawFrame()。因此会比较消耗手机性能。*/
        //当需要固定的刷新频率时,例如30帧/s,应该用Render.RENDERMODE_WHEN_DIRTY模式,然后手动控制刷新。
        //注意:必须在设置Render之后才能设置,否则会报错
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }

    /**
     * 该方法会触发GLThread恢复运行
     */
    @Override
    public void onResume() {
        super.onResume();
    }

    /**
     * 该方法会触发GLThread进入等待状态
     */
    @Override
    public void onPause() {
        super.onPause();
    }
}
//Render中一般是用OpenGl做来图形图像处理,需要了解Android提供的OpenGL ES用法
public class TestRender implements GLSurfaceView.Renderer {
    private final String TAG = getClass().getSimpleName();

    private Triangle mTriangle;
    private Square   mSquare;

    public TestRender(){

    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        ILog.d(TAG, "surfaceCreated()");
        //surface被创建后需要做的处理
        //这里是设置背景颜色
        GLES20.glClearColor(0.0f,0.0f,0.0f,1.0f);

        // 初始化一个三角形
        mTriangle = new Triangle();
        // 初始化一个正方形
        mSquare = new Square();
    }

    // 渲染窗口大小发生改变或者屏幕方法发生变化时候回调
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        ILog.d(TAG, "surfaceChanged()");

        // 设置OpenGL视口的位置与大小, 最终图像会被输出到视口上显示
        //      在此处对应的是在GlSurfaceView的Surface上现实在哪个区域
        // 该API定义时的坐标方向与OpenGl二维图形中坐标方向相同,
        //      以(0,0)为的左下角,向右为x轴正方向,向上为y轴正方向.
        //      最终输出的画面会等比例缩放到在这个区域上,但设置的区域超出实际Surface的部分不会显示。
        GLES20.glViewport(0,0,width,height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        ILog.d(TAG, "onDrawFrame()");

//        mTriangle.draw();
        mSquare.draw();
    }

}
public class Triangle {
    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;" +
                    "}";

    private FloatBuffer vertexBuffer;

    // 数组中每个顶点的坐标数
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // 按照逆时针方向:
            0.0f,  0.622008459f, 0.0f, // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
            0.5f, -0.311004243f, 0.0f  // bottom right
    };

    // 设置颜色RGBA(red green blue alpha)
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    private final int mProgram;

    public Triangle() {
        // 为存放形状的坐标,初始化顶点字节缓冲
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (坐标数 * 4 )float 占四个字节
                triangleCoords.length * 4);
        // 使用设备的本点字节序
        bb.order(ByteOrder.nativeOrder());

        // 从ByteBuffer创建一个浮点缓冲
        vertexBuffer = bb.asFloatBuffer();
        // 把坐标加入FloatBuffer中
        vertexBuffer.put(triangleCoords);
        // 设置buffer,从第一个坐标开始读
        vertexBuffer.position(0);

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

        // 创建一个空的 OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // 将vertex shader 添加到 program
        GLES20.glAttachShader(mProgram, vertexShader);

        // 将fragment shader 添加到 program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // 创建一个可执行的 OpenGL ES program
        GLES20.glLinkProgram(mProgram);
    }

    private int mPositionHandle;
    private int mColorHandle;
    private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    public void draw() {
        // 将program 添加到 OpenGL ES 环境中
        GLES20.glUseProgram(mProgram);

        // 获取指向vertex shader的成员vPosition的句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // 启用一个指向三角形的顶点数组的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // 准备三角形的坐标数据
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // 获取指向fragment shader的成员vColor的句柄
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // 设置三角形的颜色
        GLES20.glUniform4fv(mColorHandle, 1, color, 0);

        // 画三角形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

        // 禁用指向三角形的定点数组
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }

    public int loadShader(int type, String shaderCode){

        // 创建一个vertex shader 类型 (GLES20.GL_VERTEX_SHADER)
        // 或者一个 fragment shader 类型(GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);

        // 将源码添加到shader并编译
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
}
public class Square {
    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;" +
                    "}";

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    // 每个顶点的坐标数
    static final int COORDS_PER_VERTEX = 3;
    static float squareCoords[] = {
            -0.5f,  0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
            0.5f, -0.5f, 0.0f,   // bottom right
            0.5f,  0.5f, 0.0f }; // top right

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // 绘制顶点的顺序
    // 设置颜色RGBA(red green blue alpha)
//    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
    float color[] = { 0.15671875f, 0.65953125f, 0.84265625f, 1.0f };


    private final int mProgram;

    public Square() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);

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

        // 创建一个空的 OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // 将vertex shader 添加到 program
        GLES20.glAttachShader(mProgram, vertexShader);

        // 将fragment shader 添加到 program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // 创建一个可执行的 OpenGL ES program
        GLES20.glLinkProgram(mProgram);
    }

    private int mPositionHandle;
    private int mColorHandle;
//    private final int vertexCount = squareCoords.length / COORDS_PER_VERTEX;
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
    private float[] mMVPMatrix=new float[16];
    private int mMatrixHandler;

    public void draw() {
        //将程序加入到OpenGLES2.0环境
        GLES20.glUseProgram(mProgram);
        //获取变换矩阵vMatrix成员句柄
        mMatrixHandler= GLES20.glGetUniformLocation(mProgram,"vMatrix");
        //指定vMatrix的值
        GLES20.glUniformMatrix4fv(mMatrixHandler,1,false,mMVPMatrix,0);
        //获取顶点着色器的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, 1, color, 0);
        //绘制三角形
        //        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, vertexCount);
        //索引法绘制正方形
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
        //禁止顶点数组的句柄
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }

    public int loadShader(int type, String shaderCode){

        // 创建一个vertex shader 类型 (GLES20.GL_VERTEX_SHADER)
        // 或者一个 fragment shader 类型(GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);

        // 将源码添加到shader并编译
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }
}
//Activity中的调用
public class TestGlSurfaceViewActivity extends BaseActivity {
    @Override
    protected BaseActivityPresenter getPresenter() {
        return null;
    }

    private TestGlSurfaceView mGlSurfaceView;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_gl_surfaceview_activity);
        mGlSurfaceView = (TestGlSurfaceView) findViewById(R.id.gl_surface_view);

        TestRender render = new TestRender();
        //为GlSurfaceView设置渲染器Render
        //这一步很关键,当设置渲染器后,会触发GlThread线程启动,然后创建EGL环境,
        // 并在线程中根据条件判断,来对应调用Render的各个接口
        mGlSurfaceView.setRenderer(render);
    }
}
主要参考: 初识:Android中的GLSurfaceView - 简书
                  初识Android OpenGL ES - 简书
(声明:部分图片获取自网络,这里只是用于学习分享,侵删!)

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124679081