资讯详情

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

OpenGl通过它可以操作定义的跨平台图形处理接口库GPU完成图像处理。它跨平台台的,因为每个硬件制造商都按照这个接口规范实现了相应的功能,供上层调用。
由于性能相对较弱,很难支持手机OpenGl所以Android中使用的是OpenGl的子集OpenGl ES。
OpenGl它是一个跨平台的接口。它与各平台本地窗口系统的交互是在中间控制层的帮助下,中间控制层是EGL。 EGL也有自己的标准API,具体实现由各平台系统完成。
EGL是OpenGL联系当地窗口系统的桥梁负责管理OpenGL当地窗口或缓冲区的运行状态、渲染图像等功能。
在Android中,OpenGL处理的每一步都取决于EGL因此,必须首先创建这些相关功能支持EGL只有环境才能正常进行OpenGL处理。
不过Android中GLSurfaceView会自己在GLThread中完成EGL初始化、使用环境GLSurfaceView开发人员不需要自己初始化EGL环境。
1.首先可以想象Opengl有一个三维空间,可以放置很多三维物体。它有两个坐标概念:
世界坐标系(world space):是整个OpenGl空间的坐标系可以放置多个物体的3D模型
局部坐标系(local space):物体模型本身的内部坐标系,其各部分的位置坐标相对于该坐标系。
因此,当将模型添加到世界坐标系中时,通常通过矩阵转换来确定模型在世界坐标系中的位置(Model matrix)。
如果不改变模型坐标,直接添加,因为每个模型的内部坐标原点会在世界坐标系中重叠,通常会导致模型重叠。
2.想象OpenGl世界上有一个观察者,当他从不同的位置和角度看世界时,他肯定会看到不同的东西。
所以,需要确定观察点和观察方向,建立观察空间坐标系,通过矩阵变换(View matrix),
将世界坐标系中的位置坐标转换为观察空间中的坐标。这代表了观察者在自己视野前看到的物体坐标。
如果情况下,观察空间坐标系与世界坐标系重叠,观察视角向Z轴负反向。
3.观察者看到的只有部分需要通过矩阵变换进行最终输出渲染(Projection matrix)
可将观察空间中的坐标转换为切割空间,保留切割空间中各坐标轴[-1、1]范围内的部分,去除其余部分。
因此,裁剪矩阵通过定义相应的关系来定义观察空间的哪些部分"可见"的。
Android ES在2中,这个矩阵通常使用投影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编写语言。它是一个简单的程序,描述顶点或像素特征。
分为顶点着色器和片元着色器。
&nsp;  顶点着色器是针对每个顶点执行一次,用于确定顶点的位置;
        片元着色器是针对每个片元执行一次,用于确定每个片元(像素)的颜色
安卓 OpenGL ES 2.0 完全入门(一):基本概念和 hello world_Luckie stone的博客-CSDN博客
在 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 的处理过程:

弄清楚坐标系很重要,不然会找不着东南西北。下面这张图展示了 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 部分 。
(如果使用GLSurfaceView,绘制线程的EGL环境已经创建好,则可忽略这步)
编译加载着色器,链接program对象;
各种要到的缓冲区初始化,绑定到着色器;
此时可以将各种不变的数据加载到着色器,不用每次绘制都重复加载,例如:
        部分顶点坐标、与顶点坐标对应的纹理采样坐标等。
如需使用纹理,要初始化用到的纹理;
如使用FBO时,要初始化用到FBO对象以及对应的输出纹理;
设置视口,用于输出画面;
绘制是着色器来完成的,每次着色器要用到的部分数据可能不同,要重新加载,例如:
        顶点坐标、变换矩阵、本次用到的纹理等等。
绘制三角形,例如用三角形带的方式绘制多个三角形,拼接成最终的图像。
1.着色器的编译代码使用的是GLSL语言,语法与C/C++相似,着色器程序控制的是GPU的绘制过程
2.GLES20的API方法都是一堆native方法,我们是以JNI的方式调用,这些量值的空间都会分配在本地内存空间中。
  这里的句柄都是int型的ID值,与它们在本地内存中的存储位置相关联,可用于操作它们。从效果上讲,若要简单理解,可类比于内存指针。
  我们定义在java层的坐标值之类的,也是通过JNI方式传递过去赋值给native内存空间上的对应量。
  GLES20的API文档地址:https://www.khronos.org/registry/OpenGL-Refpages/es2.0/
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)     //右矩阵的起始位置(偏移量)
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

标签: 电感磁珠upz1005d121

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台