1 OpenGL ES简介

谈到OpenGL ES,首先我们应该先去了解一下Android的基本架构,基本架构下图:


学习 Android 平台 OpenGL ES API,了解 OpenGL 开发的基本流程,使用 OpenGL 绘制一个三角形_第1张图片 Android架构图

这里我们可以找到Libraries里面有我们目前要接触的库,即OpenGL ES。
根据上图可以知道Android 目前是支持使用开放的图形库的,特别是通过OpenGL ES API来支持高性能的2D和3D图形。OpenGL是一个跨平台的图形API。为3D图形处理硬件指定了一个标准的软件接口。OpenGL ES 是适用于嵌入式设备的OpenGL规范。
Android 支持OpenGL ES API版本的详细状态是:

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及以上版本支持

2 OpenGL ES使用

在了解OpenGL的使用之前,我们需要了解两个基本类别的Android框架:GLSurfaceView和GLSurfaceView.Renderer。

2.1 GLSurfaceView

GLSurfaceView从名字就可以看出,它是一个SurfaceView。看源码可知,GLSurfaceView继承自SurfaceView,并增加了Renderer,它的作用就是专门为OpenGL显示渲染使用的。

2.2 GLSurfaceView.Renderer

此接口定义了在GLSurfaceView中绘制图形所需的方法。您必须将此接口的实现作为单独的类提供,并使用GLSurfaceView.setRenderer()将其附加到您的GLSurfaceView实例。

GLSurfaceView.Renderer要求实现以下方法:

  • onSurfaceCreated():创建GLSurfaceView时,系统调用一次该方法。使用此方法执行只需要执行一次的操作,例如设置OpenGL环境参数或初始化OpenGL图形对象。
  • onDrawFrame():系统在每次重画GLSurfaceView时调用这个方法。使用此方法作为绘制(和重新绘制)图形对象的主要执行方法。
  • onSurfaceChanged():当GLSurfaceView的发生变化时,系统调用此方法,这些变化包括GLSurfaceView的大小或设备屏幕方向的变化。例如:设备从纵向变为横向时,系统调用此方法。我们应该使用此方法来响应GLSurfaceView容器的改变。

介绍完了GlSurfaceView和GlSurfaceView.renderer之后,接下来说下如何使用GlSurfaceView:

1、创建一个GlSurfaceView
2、为这个GlSurfaceView设置渲染
3、在GlSurfaceView.renderer中绘制处理显示数据

3 OpenGL ES绘制图形

3.1 OpenGL ES环境搭建

为了在Android应用程序中使用OpenGL ES绘制图形,必须要为他们创建一个视图容器。其中最直接或者最常用的方式就是实现一个GLSurfaceView和一个GLSurfaceView.Renderer。GLSurfaceView是用OpenGL绘制图形的视图容器,GLSurfaceView.Renderer控制在该视图内绘制的内容。

3.1.1 在Manifest中声明OpenGL ES使用

了让你的应用程序能够使用OpenGL ES 2.0的API,你必须添加以下声明到manifest:

如果你的应用程序需要使用纹理压缩,你还需要声明你的应用程序需要支持哪种压缩格式,以便他们安装在兼容的设备上。

3.1.2 创建一个Activity 用于展示OpenGL ES 图形

使用OpenGL ES的应用程序的Activity和其他应用程的Activity一样,不同的地方在于你设置的Activity的布局。在许多使用OpenGL ES的app中,你可以添加TextView,Button和ListView,还可以添加GLSurfaceView。

下面的代码展示了使用GLSurfaceView做为主视图的基本实现:

public class OpenGLES20Activity extends Activity {    private GLSurfaceView mGLView;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        // Create a GLSurfaceView instance and set it        // as the ContentView for this Activity.        mGLView = new MyGLSurfaceView(this);        setContentView(mGLView);    }}

3.1.3 创建GLSurfaceView对象

GLSurfaceView是一个特殊的View,通过这个View你可以绘制OpenGL图像。但是View本身没有做太多的事情,主要的绘制是通过设置在View里面的GLSurfaceView.Renderer 来控制的。实际上,创建这个对象的代码是很少的,你能会想尝试跳过extends的操作,只去创建一个没有被修改的GLSurfaceView实例,但是不建议这样去做。因为在某些情况下,你需要扩展这个类来捕获触摸的事件,捕获触摸的事件的方式会在后面的文章里面做介绍。
GLSurfaceView的基本代码很少,为了快速的实现,通常会在使用它的Activity中创建一个内部类来做实现:

class MyGLSurfaceView extends GLSurfaceView {    private final MyGLRenderer mRenderer;    public MyGLSurfaceView(Context context){        super(context);        // Create an OpenGL ES 2.0 context        setEGLContextClientVersion(2);        mRenderer = new MyGLRenderer();        // Set the Renderer for drawing on the GLSurfaceView        setRenderer(mRenderer);    }}

你可以通过设置GLSurfaceView.RENDERMODE_WHEN_DIRTY来让你的GLSurfaceView监听到数据变化的时候再去刷新,即修改GLSurfaceView的渲染模式。这个设置可以防止重绘GLSurfaceView,直到你调用了requestRender(),这个设置在默写层面上来说,对你的APP是更有好处的。

3.1.4 创建一个GLSurfaceView.Renderer类

实现了GLSurfaceView.Renderer 类才是真正算是开始能够在应用中使用OpenGL ES。这个类控制着与它关联的GLSurfaceView 绘制的内容。在Renderer 里面有三个方法能够被Android系统调用,以便知道在GLSurfaceView绘制什么以及如何绘制:

  • onSurfaceCreated() - 在View的OpenGL环境被创建的时候调用。
  • onDrawFrame() - 每一次View的重绘都会调用
  • onSurfaceChanged() - 如果视图的几何形状发生变化(例如,当设备的屏幕方向改变时),则调用此方法。

下面是使用OpenGL ES 渲染器的基本实现,仅仅做的事情就是在GLSurfaceView绘制一个黑色背景。

public class MyGLRenderer implements GLSurfaceView.Renderer {    public void onSurfaceCreated(GL10 unused, EGLConfig config) {        // Set the background frame color        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);    }    public void onDrawFrame(GL10 unused) {        // Redraw background color        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);    }    public void onSurfaceChanged(GL10 unused, int width, int height) {        GLES20.glViewport(0, 0, width, height);    }}

3.2 OpenGL ES定义形状

我们能够配置好基本的Android OpenGL 使用的环境。但是如果我们不了解OpenGL ES如何定义图像的一些基本知识就使用OpenGL ES进行绘图还是有点棘手的。所以能够在OpenGL ES的View里面定义要绘制的形状是进行高端绘图操作的第一步。
下面讲解Android设备屏幕相关的OpenGL ES坐标系统,定义形状,形状面的基础知识,以及定义三角形和正方形。

3.2.1 定义三角形

OpenGL ES允许你使用三维空间坐标系定义绘制的图像,所以你在绘制一个三角形之前必须要先定义它的坐标。在OpenGL中,这样做的典型方法是为坐标定义浮点数的顶点数组。
为了获得最大的效率,可以将这些坐标写入ByteBuffer,并传递到OpenGL ES图形管道进行处理。

public class Triangle {    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    };    // 设置颜色,分别为red, green, blue 和alpha (opacity)    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };    public Triangle() {        // // 为存放形状的坐标,初始化顶点字节缓冲        ByteBuffer bb = ByteBuffer.allocateDirect(                // (坐标数 * 4)float占四字节        // 使用设备的本点字节序        bb.order(ByteOrder.nativeOrder());        // 从ByteBuffer创建一个浮点缓冲        vertexBuffer = bb.asFloatBuffer();        //把坐标们加入FloatBuffer中        vertexBuffer.put(triangleCoords);        //设置buffer,从第一个坐标开始读        vertexBuffer.position(0);    }}

请注意,此图形的坐标以逆时针顺序定义。 绘图顺序非常重要,因为它定义了哪一面是您通常想要绘制的图形的正面,以及背面。

3.2.2 定义正方形

可以看到,在OpenGL里面定义一个三角形很简单。但是如果你想要得到一个更复杂一点的东西呢?比如一个正方形?能够找到很多办法来作到这一点,但是在OpenGL里面绘制这个图形的方式是将两个三角形画在一起。


学习 Android 平台 OpenGL ES API,了解 OpenGL 开发的基本流程,使用 OpenGL 绘制一个三角形_第2张图片 image.png

同样,你应该以逆时针的顺序为这两个代表这个形状的三角形定义顶点,并将这些值放在一个ByteBuffer中。 为避免定义每个三角形共享的两个坐标两次,请使用图纸列表告诉OpenGL ES图形管道如何绘制这些顶点。 这是这个形状的代码:

public class Square {    //顶点缓冲区    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 }; //顶点的绘制顺序    // 设置图形的RGB值和透明度    public Square() {        // initialize vertex byte buffer for shape coordinates        ByteBuffer bb = ByteBuffer.allocateDirect(        // (坐标数 * 4))                squareCoords.length * 4);        bb.order(ByteOrder.nativeOrder());        vertexBuffer = bb.asFloatBuffer();        vertexBuffer.put(squareCoords);        vertexBuffer.position(0);        //为绘制列表初始化字节缓冲        ByteBuffer dlb = ByteBuffer.allocateDirect(        // (对应顺序的坐标数 * 2)short是2字节                drawOrder.length * 2);        dlb.order(ByteOrder.nativeOrder());        drawListBuffer = dlb.asShortBuffer();        drawListBuffer.put(drawOrder);        drawListBuffer.position(0);    }}

这个例子让你了解用OpenGL创建更复杂的形状的过程。 一般来说,您使用三角形的集合来绘制对象。

3.3 OpenGL ES绘制形状

3.3.1 初始化形状

在你做任何绘制操作之前,你必须要初始化并加载你准备绘制的形状。除非形状的结构(指原始的坐标)在执行过程中发生改变,你都应该在你的Renderer的方法onSurfaceCreated()中进行内存和效率方面的初始化工作。

public class MyGLRenderer implements GLSurfaceView.Renderer {    ...    private Triangle mTriangle;    private Square   mSquare;    public void onSurfaceCreated(GL10 unused, EGLConfig config) {        ...        // initialize a triangle        mTriangle = new Triangle();        // initialize a square        mSquare = new Square();    }    ...}

3.3.2 绘制形状

使用OpenGLES 2.0画一个定义好的形状需要比较多的代码,因为你必须为图形渲染管线提供一大堆信息。特别的,你必须定义以下几个东西:

  • Vertex Shader - 用于渲染形状的顶点的OpenGLES 图形代码。
  • Fragment Shader - 用于渲染形状的外观(颜色或纹理)的OpenGLES 代码。
  • Program - 一个OpenGLES对象,包含了你想要用来绘制一个或多个形状的shader。

你至少需要一个vertexshader来绘制一个形状和一个fragmentshader来为形状上色。这些形状必须被编译然后被添加到一个OpenGLES program中,program之后被用来绘制形状。下面是一个展示如何定义一个可以用来绘制形状的基本shader的例子:

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;" +        "}";    ...}

Shader们包含了OpenGLShading Language (GLSL)代码,必须在使用前编译。要编译这些代码,在你的Renderer类中创建一个工具类方法:

private int loadShader(int type, String shaderCode) {        //根据type创建顶点着色器或者片元着色器        //创建一个vertex shader类型(GLES20.GL_VERTEX_SHADER)        //或一个fragment shader类型(GLES20.GL_FRAGMENT_SHADER)        int shader = GLES20.glCreateShader(type);        //将资源加入到着色器中,并编译        GLES20.glShaderSource(shader, shaderCode);        GLES20.glCompileShader(shader);        return shader;    }

为了绘制你的形状,你必须编译shader代码,添加它们到一个OpenGLES program 对象然后链接这个program。在renderer对象的构造器中做这些事情,从而只需做一次即可。

注:编译OpenGLES shader们和链接linkingprogram们是很耗CPU的,所以你应该避免多次做这些事。如果在运行时你不知道shader的内容,你应该只创建一次code然后缓存它们以避免多次创建。

public class Triangle() {    ...    private final int mProgram;    public Triangle() {        ...        //编译shader代码        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,                                        vertexShaderCode);        int fragmentShader = MyGLRenderer.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);    }}

此时,你已经准备好增加真正的绘制调用了。需要为渲染管线指定很多参数来告诉它你想画什么以及如何画。因为绘制操作因形状而异,让你的形状类包含自己的绘制逻辑是个很好主意。

创建一个draw()方法负责绘制形状。下面的代码设置位置和颜色值到形状的vertexshader和fragmentshader,然后执行绘制功能:

private int mPositionHandle;private int mColorHandle;//顶点个数private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX; //顶点之间的偏移量private final int vertexStride = COORDS_PER_VERTEX * 4; // 每个顶点四个字节public void draw() {    // // 添加program到OpenGL ES环境中    GLES20.glUseProgram(mProgram);    // 获取指向vertex shader(顶点着色器)的成员vPosition的handle    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");    // 启用一个指向三角形的顶点数组的handle    GLES20.glEnableVertexAttribArray(mPositionHandle);    // 准备三角形的坐标数据    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,                                 GLES20.GL_FLOAT, false,                                 vertexStride, vertexBuffer);    // 获取指向fragment shader(片元着色器)的成员vColor的handle    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");    // 设置绘制三角形的颜色    GLES20.glUniform4fv(mColorHandle, 1, color, 0);    // 绘制三角形    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);    // Disable vertex array    GLES20.glDisableVertexAttribArray(mPositionHandle);}

一旦完成了所有这些代码,绘制该对象只需要在渲染器的onDrawFrame()方法中调用draw()方法:

public void onDrawFrame(GL10 unused) {    ...    mTriangle.draw();}

当你运行程序的时候,你就应该看到以下的内容:

学习 Android 平台 OpenGL ES API,了解 OpenGL 开发的基本流程,使用 OpenGL 绘制一个三角形_第3张图片 竖屏 学习 Android 平台 OpenGL ES API,了解 OpenGL 开发的基本流程,使用 OpenGL 绘制一个三角形_第4张图片 横屏

此例子中的代码还有很多问题。首先,它不会打动你和你的朋友。其次,三角形会在你从竖屏变为横屏时被压扁。三角形变形的原因是其顶点们没有跟据屏幕的宽高比进行修正。而且这里展示出来的三角形是静止的,这样的图形是有点无聊的,在“添加动画”的文章中,我们会使用OpenGL ES 的视图管线来旋转此形状。
源码地址:https://github.com/Xiaoben336/OpenGLES20Study
在下一篇文章,我们将使用投影和摄像头视图来修正显示的问题。

更多相关文章

  1. Android调用摄像头识别图片的形状和颜色怎么做
  2. 从xml中改变checkBox大小和形状
  3. Android OpenGL入门示例:绘制三角形和正方形 (附完整源码)
  4. Android 3D 旋转的三角形(一)
  5. Android UI---自定义形状shape
  6. Android 自定义Button形状
  7. Android drawable 三角形
  8. 改变button按钮的形状
  9. Android下使用OpenGL绘制三角形

随机推荐

  1. MTP in Android
  2. Android 抢购功能(时间戳之间的倒计时)
  3. Android 获取网络视频某一帧图片
  4. Android(安卓)Lottie动画初探
  5. Unity3D和Android之间的方法交互(jar模式
  6. Android 渗透测试学习手册(一)Android 安全
  7. Android使用AOP
  8. SVG 矢量图和矢量动画介绍
  9. Android BootLoader及两种刷机模式fastbo
  10. Android系统启动过程---uboot,kernel,and