Android OpenGl ES使用原理总结与代码示例

一、相关概念简介:
OpenGl :
    OpenGl是一个定义好的跨平台图形处理接口库,通过它可操作GPU来完成图像处理。它跨平台是因为各个硬件厂家都按照这套接口规范具体实现了对应功能,供上层调用。
OpenGl ES:
    手机由于性能相对较弱,难以支持OpenGl的全部功能,所以Android中使用的是OpenGl的子集OpenGl ES。
EGL:
    OpenGl是一套跨平台的接口,它与各个平台本地窗口系统之间的交互,是借助于一个中间控制层,这个中间控制层就是EGL。 EGL也有自己的一套标准API,由各个平台的系统来完成其具体实现。
    EGL是OpenGL和本地窗口体系进行联系的桥梁,负责管理OpenGL的运行状态、渲染图像到本地窗口或缓冲区等功能。
    在Android中,OpenGL的每一步处理,都需要依赖于EGL提供的这些相关功能支持,所以必须先创建EGL环境,才能正常进行OpenGL处理。
    不过Android中GLSurfaceView会自己在GLThread中完成EGL环境的初始化,使用GLSurfaceView时,开发者并不需要自己来初始化EGL环境。
二、OpenGL原理大致理解:
1.首先可以想象Opengl内部有一个三维空间,里面可以放置很多三维物体。它有两个坐标系概念:
        世界坐标系(world space):是整个OpenGl空间的坐标系,里面可以放置多个物体的3D模型
        局部坐标系(local space):物体模型自己内部的坐标系,模型生成时其各部分的位置坐标是相对此坐标系的。
        因此,把模型添加到世界坐标系上时,需要确定模型在世界坐标系中的位置,这一般是通过矩阵变换来处理的(Model matrix)。
        若不变换模型坐标,直接添加,由于各个模型内部的坐标原点在世界坐标系中会重叠,一般会导致模型重叠在一起。
2.想象OpenGl世界中有一个观察者,他从不同的位置和角度看这个世界时,看到的东西肯定也不一样。
        所以,需要确定观察点和观察方向,建立观察空间坐标系,通过矩阵变换(View matrix),
        将世界坐标系中的位置坐标转为为观察空间中的坐标。这代表着观察者在自己视野前方所看到的物体坐标。
        如果不加设置,默认情况下,观察空间坐标系与世界坐标系重合,观察视角朝向Z轴负反向。
3.观察者看到的景象中只有部分是需要最终输出渲染的,通过矩阵变换(Projection matrix)
        可以将观察空间中坐标变换到裁剪空间中,裁剪空间中各个坐标轴上[-1,1]范围内的部分会被保留,其余会被去除。
        因此这个裁剪矩阵通过定义对应关系,从而定义了观察空间中哪些部分是"可见"的。
        Android ES2中,这个矩阵一般是调用投影方式的API,并设置观察范围的相关参数来生成的, 所以这个矩阵变换相当于同时约束了观察范围和投影方式。
        Android ES2中包含了两种投影方式,
            正交投影:观察到的物体不会因为离观察点的远近有差别,即近处的点和远处的点,最终投影在输出画面上的大小都一样大;
            透视投影:观察到的物体随距观察点的远近产生变化,近大远小。
4.然后Android中,OpenGl会自动将裁剪空间上的坐标计算成屏幕指定显示区域上的坐标,经过相应渲染处理后,并最终显示出来。 (这一步不用考虑OpenGL空间中的坐标系与Android屏幕坐标系的异同,API内部自己会正确计算出对应坐标的。)
    最终,裁剪空间 = (Projection matrix) * (View matrix) * (Model matrix) * 模型本地空间;
    最终可观察到的部分,是裁剪空间中各个坐标轴上[-1,1]范围内的部分。
    但我们在屏幕上看到的只能是一个二维画面,所以OpenGl会根据我们的设置,将这部分三维物体映射渲染成一个看起来"像三维物体"的二维画面。
##.内部的一些概念介绍:
    在OpenGl ES三维空间中,本质上讲能生成的只有点、线、三角形,任何复杂的模型图形都是用三角形拼接成的。
    (查资料,好像传统OpenGl中应该是直接支持更复杂的四边形、多边形的。OpenGl ES只是子集,应该做了简化)
1.OpenGL 里有两个最基本的概念:Vertex (顶点)和 Fragment(片元)。
        Vertix(顶点):一切图形都从 Vertix 开始,Vertix 序列围成了一个图形;
        Fragment(片元):是指OpenGl图形映射显示到屏幕上时,那一个个的映射区域。
2.光栅化(Rasterization):光栅化是把点、线、三角形映射到屏幕上的像素点的过程(每个映射区域叫一个 Fragment), 也就是生成 Fragment 的过程。
        通常一个 Fragment 对应于屏幕上的一个像素, 但高分辨率的屏幕可能会用多个像素点映射到一个 Fragment,以减少 GPU 的工作。
3.OpenGl空间对应的x、y、z坐标,都在[-1,1]的范围内。
        这是一个数学概念上的空间范围,这个范围内的坐标称之为归一化设备坐标。 它是独立于屏幕实际的尺寸和形状的。
4.着色器(Shader):OpenGl ES2中使用的,在GPU上运行的小程序。可通过处理它们来处理顶点或片元。
        此程序使用OpenGL ES SL语言来编写。它是一个描述顶点或像素特性的简单程序。
        分为顶点着色器、片元着色器。
        顶点着色器是针对每个顶点执行一次,用于确定顶点的位置;
        片元着色器是针对每个片元执行一次,用于确定每个片元(像素)的颜色
在 OpenGL 的世界里,我们只能画点、线、三角形,复杂的图形都是由三角形构成的。
在 OpenGL 里有两个最基本的概念:Vertex (顶点)和 Fragment(片元)。一切图形都从 Vertix 开始,Vertix 序列围成了一个图形。那什么是 Fragment 呢?为此我们需要了解一下光栅化(Rasterization):光栅化是把点、线、三角形映射到屏幕上的像素点的过程(每个映射区域叫一个 Fragment),也就是生成 Fragment 的过程。通常一个 Fragment 对应于屏幕上的一个像素,但高分辨率的屏幕可能会用多个像素点映射到一个 Fragment,以减少 GPU 的工作。
接下来介绍 Shader(着色器程序):Shader 用来描述如何绘制(渲染),GLSL 是 OpenGL 的编程语言,全称 OpenGL Shader Language,它的语法类似于 C 语言。OpenGL 渲染需要两种 Shader:Vertex Shader 和 Fragment Shader。
每个 Vertex 都会执行一遍 Vertex Shader,以确定 Vertex 的最终位置,其 main 函数中必须设置  gl_Position  全局变量,它将作为该 Vertex 的最终位置,进而把 Vertex 组合(assemble)成点、线、三角形。光栅化之后,每个 Fragment 都会执行一次 Fragment Shader,以确定每个 Fragment 的颜色,其 main 函数中必须设置  gl_FragColor  全局变量,它将作为该 Fragment 的最终颜色。
下面是 OpenGL 的处理过程:

2. 坐标系

弄清楚坐标系很重要,不然会找不着东南西北。下面这张图展示了 OpenGL 通常的处理流程中各个环节的坐标系,以及坐标系之间的转换操作:
下面对图中的几个概念作简单说明:
Local space:我们为每个物体建好模型的时候,它们的坐标就是 Local space 坐标;
World space:当我们要绘制多个物体时,如果直接使用 Local space 的坐标(把所有物体的原点放在一起),那它们很可能会发生重叠,因此我们需要把它们进行合理的移动、排布,最终各自的坐标就是 World space 的坐标了;
Model matrix:把 Local space 坐标转换到 World space 坐标所使用的变换矩阵,它是针对每个物体做不同的变换;
View space:通常也叫 Camera space 或者 Eye space,是从观察者(也就是我们自己)所在的位置出发,所看到的空间;
View matrix:把 World space 坐标转换到 View space 坐标所使用的变换矩阵,它相当于是在移动相机位置,实际上是反方向移动整个场景(所有物体);
Clip space:OpenGL 只会渲染坐标值范围在  [-1, 1]  的内容,超出这个范围的内容都会被裁剪掉,这个范围的空间就叫 Clip space,Clip space 的坐标系也叫 normalized device coordinate(NDC);
Projection matrix:把 View space 坐标转换到 Clip space 坐标所使用的变换矩阵,它会指定一个可见的范围,只有这个范围内的点才会转换到 NDC 中,而这个范围被称作视锥(frustum);projection matrix 有三种创建方式:正投影(Orthographic projection),透视投影(Perspective projection),以及 3D 投影(3D projection);前两种比较常用;
Screen space:屏幕上的空间, glViewport  调用指定的区域;
Viewport transform:这一步是 Open GL 自动完成的,把 Clip space 坐标转换到 Screen space 坐标;
通常说的 Model,View,Projection 这三种变换都是针对 Vertex 坐标做的变换,也就是:
更多关于坐标系的内容,可以阅读  Learn OpenGL 的 Coordinate Systems 部分
三、一般使用流程与讲解
1.环境初始化:为绘制所用线程创建EGL环境
(如果使用GLSurfaceView,绘制线程的EGL环境已经创建好,则可忽略这步)
2.着色器和纹理相关的初始化:
编译加载着色器,链接program对象;
各种要到的缓冲区初始化,绑定到着色器;
此时可以将各种不变的数据加载到着色器,不用每次绘制都重复加载,例如:
        部分顶点坐标、与顶点坐标对应的纹理采样坐标等。
如需使用纹理,要初始化用到的纹理;
如使用FBO时,要初始化用到FBO对象以及对应的输出纹理;
3.绘制逻辑:
设置视口,用于输出画面;
绘制是着色器来完成的,每次着色器要用到的部分数据可能不同,要重新加载,例如:
        顶点坐标、变换矩阵、本次用到的纹理等等。
绘制三角形,例如用三角形带的方式绘制多个三角形,拼接成最终的图像。
##.要点:
1.着色器的编译代码使用的是GLSL语言,语法与C/C++相似,着色器程序控制的是GPU的绘制过程
2.GLES20的API方法都是一堆native方法,我们是以JNI的方式调用,这些量值的空间都会分配在本地内存空间中。
  这里的句柄都是int型的ID值,与它们在本地内存中的存储位置相关联,可用于操作它们。从效果上讲,若要简单理解,可类比于内存指针。
  我们定义在java层的坐标值之类的,也是通过JNI方式传递过去赋值给native内存空间上的对应量。
3.将坐标数据载入编码器的示例讲解:
  GLES20.glVertexAttribPointer(//将坐标数据载入
        aPositionLoc,  //id
        OFFSET_PER_VERTEX, //告诉他用几个偏移量来描述一个点
        GLES20.GL_FLOAT, false,
        BYTE_PER_VERTEX, //一个顶点需要多少个字节的偏移量
        mVertexFloatBuffer); //缓冲区
4.视口设置与讲解:
// 设置OpenGL视口的位置与大小, 最终图像会被输出到屏幕或目标纹理的视口对应的区域显示
//    例如图像最终被渲染到GlSurfaceView上,则在此处定义的是显示在GlSurfaceView的Surface上的哪个区域
// 注意该API所采用的坐标系,与OpenGl中的默认纹理坐标系方向相同,
//    即以Surface的左下角为原点(0,0),向右为x轴正方向,向上为y轴正方向.
// OpenGl世界坐标系上经过层层矩阵变换,最后被裁剪空间选中的部分会投射出一副二维画面显示在这个区域上,
//    这张输出画面会拉伸显示到这个区域上,但若设置的视口区域超出实际Surface,超出的部分不会显示。
GLES20.glViewport(0,0, outputWidth, outputHeight);
5.如何绘制多个三角形
前面说过,OpenGL ES中任何复杂的图形都是三角形拼接成的。有多种绘制方式。
5.1 GLES20.GL_TRIANGLES:使用顶点数组中前三个顶点绘制一个三角形,只是绘制一个。
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, VERTEX_COUNT);

5.2 GLES20.GL_TRIANGLE_FAN:任意三个顶点间,都会绘制一个三角形。绘制次数较多。
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, VERTEX_COUNT);

5.3 GLES20.GL_TRIANGLE_STRIP:三角形带的方式
5.3.1自动绘制,开始的3个点描述一个三角形,后面每多一个点,与前面两个点在一起,多绘制一个三角形。
             这种方式不需要定义绘制顺序,但需要根据需要设置好顶点数组中顶点的次序。
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0, VERTEX_COUNT);
5.3.2自定义三角形绘制顺序,在indexBuffer中定义自己的绘制顺序,按照绘制顺序来绘制。
(这是本人喜欢用的方式)
GLES20.glDrawElements(GLES20.GL_TRIANGLES, SQUARE_INDEX.length, GLES20.GL_UNSIGNED_SHORT, mIndexBuffer);
//使用绘制三角形带的方式,来绘制各个三角形,需要在数组中定义绘制顺序
//自定义的绘制顺序,里面值为对应顶点在顶点数组中的索引值,每三个顶点为一组围成一个三角形
//  此处的顺序是,先绘制0-1-2围成的三角形,然后绘制0-3-2对应的三角形,最终拼成一个矩形
private static short SQUARE_INDEX[] = {0 , 1, 2, 0, 3, 2};
6.相关变换矩阵处理讲解:
6.1.获取观察矩阵,即变换到观察者空间的矩阵(一般称为相机矩阵,这里为了避免与Android相机的相关名词混淆,改了个称呼)
        Matrix.setLookAtM (float[] rm,      //接收相机变换矩阵
        int rmOffset,       //变换矩阵的起始位置(偏移量)
        float eyeX,float eyeY, float eyeZ,   //相机位置
        float centerX,float centerY,float centerZ,  //观测点位置
        float upX,float upY,float upZ)  //up向量在xyz上的分量
6.2.获取投影矩阵
通过设置偏移值和x轴、y轴、z轴上的取值范围,会生成相应的投影矩阵,存储在传入的矩阵数组中。
选取的范围内的坐标,通过该矩阵变换后会落在OpenGl裁剪空间[-1,1]范围内,即选取范围外的坐标点都会被裁减掉。
这里生成的矩阵一般作为裁剪矩阵使用。
6.2.1正交投影
        Matrix.orthoM (float[] m,           //接收正交投影的变换矩阵
        int mOffset,        //变换矩阵的起始位置(偏移量)
        float left,         //x轴的最小范围
        float right,        //x轴的最大范围
        float bottom,       //y轴的最小范围
        float top,          //y轴的最大范围
        float near,         //z轴的最小范围,即相对观察点近视面
        float far);          //z轴的最大范围,即相对观察点远视面
6.2.2透视投影
        Matrix.frustumM (float[] m,
        int mOffset,
        float left,
        float right,
        float bottom,
        float top,
        float near,
        float far)
6.3.矩阵乘法
        Matrix.multiplyMM (float[] result, //接收相乘结果
        int resultOffset,  //接收矩阵的起始位置(偏移量)
        float[] lhs,       //左矩阵
        int lhsOffset,     //左矩阵的起始位置(偏移量)
        float[] rhs,       //右矩阵
        int rhsOffset)     //右矩阵的起始位置(偏移量)
四、简单代码示例与讲解(见注释):
(这里只是提供了用OpenGL绘制一个三角形和一个矩形的示例,不过就算是要贴相机纹理,其实所需要绘制的模型也只是四个顶点的一个矩形)
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;
    }
}

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);
    }
}

参考剪藏:
安卓 OpenGL ES 2.0 完全入门(一):基本概念和 hello world - android开发笔记 - CSDN博客
OpenGL ES API的说明文档网站: OpenGL ES 2.0 Reference Pages

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124688638
今日推荐