Android开发培训(08)--使用openGL ES作图

图像和动画

这些文章讲述教你怎么使用图像写出更有竞争力的app,如果你想要更好的用户体验以及用户更好的体验效果,下面的这些类将会帮助到你。

第一章 使用OpenGL ES 展示图像

为了使openGLES在你的app上运行,你需要写一个view 容器。最简单的方式就是实现GLSurfaceView和GLSurfaceView.Renderer.

GLSurfaceView是一个view容器opengl 可以画图像,GLSurfaceView.Renderer控制你在上面可以画什么图像。需要获取更多的信息,可以查看OpenGL ES开发手册。

GLSurfaceView只是一种你的应用和OpenGL ES打交道的方式,对于全屏幕或者基本上全屏幕的图像应用来说是可行的。开发者如果只是在他们的不居中显示很小的一部分opengl 则应该使用TextureView.还有一种就是开发者自己设计,它可以建立在SurfaceView之上,但是这需要写很多额外的代码。

这篇文章讲述如何使用GLSurfaceView 和GLSurceView.Renderer实现一个很小的app.

1. 在manifest文件中申明使用OpengGL ES

为了让你的app能够使用OpenGL 2.0,你必须加入下面的申明。

如果你的程序使用texture压缩,你还需要申明下面两个你的app应该支持的格式,它只会安装在兼容的机器中。


更多的压缩兼容的信息,请查看相关的网站。

2. 给OpenGL ES 创建一个Activity

android程序使用OpenGL ES使用activity就像其它的程序有自己的接口一样,和其它程序最大的不同就是你放在你布局文件中的东西。大部分的程序会用TextView, Button, ListView这些组件,但是如果你使用OpenGL 的话,你应该加入GLSurfaceView.

下面的代码示例展示了一个最小的实现,一个activity,使用一个GLSurfaceView作为它的基础view

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. 建立GLSurfaceView对象

一个GLSurfaceView指定了你在哪里可以画GL图像,它并没有做其它的什么事情。真正的画图是被GLSurfaceView.Renderer类控制的。实际上,这个对象的代码很少,你可以创建一个不能修改的GLSurfaceView实例,但是不要那么做。当你需要捕获触摸事件的时候你需要拓展这个类,这些会在Responding to Touch Event章节讲述。

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的实现其中要做的一件事情就是设置render模式,这里使用 GLSurfaceView.RENDERMODE_WHEN_DIRTY

// Render the view only when there is a change in the drawing data
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
这个模式除了你调用requestRender()进行重新渲染之外,GLSurfaceView的帧都不会再重画对于简单的app来说这更有效。

4. 建立一个渲染类

这是GLSurfaceView.Renderer类的实现,这才是使用OpenGL ES功能的开始。这个类控制了你可以在GLSurfaceView中画什么东西。这里有三个方法会被系统调用,

onSurfaceCreated 当建立起来OpengGL Es的环境之后会调用一次

onDrawFrame() 每次重新绘制的时候会被调用

onSurfaceChanged() view改变的时候会被调用,比如每次设备的屏幕旋转之后会被调用

下面是一个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);
    }
}
这是openGL ES一个创建一个简单的程序的所有,只是显示了一个黑色的背景,没有做其它的任何有趣的事情,通过创建这些类你,会对opengl 绘图有大致的了解。

当你使用ES 2.0的时候你有个疑问为什么这些方法有个GL10参数,那些方法就是简单地重新使用2.0api,保持android 框架的代码更简洁。

如果你已经使用过OpenGL ES 的api绘图,那么上面教的这些就可以让你开始写程序了,如果你还对OpengGL 不熟悉,可以继续看下面的内容。

一个最简单的Openg gl程序,绿色的背景

建立一个MainActivity,然后创建两个类,一个是SurfaceView类主要是显示布局用,一个是Render类,真正的绘图的地方。

package com.example.www.graphictest;

import android.opengl.GLSurfaceView;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    private GLSurfaceView mGLView;

    @Override
    protected 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);
    }
}
package com.example.www.graphictest;

import android.content.Context;
import android.opengl.GLSurfaceView;

/**
 * Created by wang on 17-8-26.
 */

class MyGLSurfaceView extends GLSurfaceView {
    private final MyGLRenderer mRenderer;
    public MyGLSurfaceView(Context context) {
        super(context);
        // Create a OpenGL ES 3.0 context
        setEGLContextClientVersion(3);
        mRenderer = new MyGLRenderer();
        setRenderer(mRenderer);

        // Render the view only when there is a change in the drawing data
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }
}
package com.example.www.graphictest;

import android.opengl.GLES30;
import android.opengl.GLSurfaceView;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * Created by wang on 17-8-26.
 */

class MyGLRenderer implements GLSurfaceView.Renderer{
    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        // Set the background frame color
        GLES30.glClearColor(0.0f, 0.4f, 0.3f, 1.0f);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        // Adjust the viewport based on geometry changes, such as screen rotaion
        GLES30.glViewport(0, 0, width, height);

    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        // Draw background color
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);

    }
}
完成之后运行会看到绿色的背景。


第二章 定义形状

在需要将形状画上opengl之前,你先需要创建你的形状。如果你在写opengl程序而对于opengl一无所知的话会有些尴尬,因为基本的原理都是使用的opengl。

这篇教程解释opengl的坐标系统,如何关联上android设备的屏幕,基本的形状的定义,形状的面,已经三角形和正方形也都会讲述。

1. 定义三角形

Opengl 允许你定义三维的图形,所以在你开始画三角形之前,你需要定义自己的坐标系。

在Opengl中,最典型的方式就是定义一个为这个坐标系定义一个顶点数组。为了最有效,你可以把这些数据写入一个ByteBuffer中,这个通过opengl的图形管道传输进去交给它们处理。

下面是定义了一个三角形

public class Triangle {

    private FloatBuffer vertexBuffer;

    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;
    static float triangleCoords[] = {   // in counterclockwise order:
             0.0f,  0.622008459f, 0.0f, // top
            -0.5f, -0.311004243f, 0.0f, // bottom left
             0.5f, -0.311004243f, 0.0f  // bottom right
    };

    // Set color with red, green, blue and alpha (opacity) values
    float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };

    public Triangle() {
        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (number of coordinate values * 4 bytes per float)
                triangleCoords.length * 4);
        // use the device hardware's native byte order
        bb.order(ByteOrder.nativeOrder());

        // create a floating point buffer from the ByteBuffer
        vertexBuffer = bb.asFloatBuffer();
        // add the coordinates to the FloatBuffer
        vertexBuffer.put(triangleCoords);
        // set the buffer to read the first coordinate
        vertexBuffer.position(0);
    }
}
默认的,opengl es假设坐标系统的原点在[0,0,0]在GLSurfaceView的中心,[1, 1,0]在它的右上角,[-1, -1, 0]在他的左下角。

更多的参考,请看opengl Es参考手册。

这个形状定义为逆时针,他们的绘画的先后顺序很重要,因为它定义了哪部分在形状的最前面,更多的关于opengl es隐藏和覆盖的信息,查看开发者手册。

2. 定义正方形

定义三角形很简单,但是如果你想更复杂一点,定义一个正方形呢?这里有集中常见的方法,但是最常用的方法就是画两个三角形拼成一个正方形。

Android开发培训(08)--使用openGL ES作图_第1张图片

如上图所示使用两个三角形画一个正方形。

再次说明,你应该定义两个三角形为逆时针,把他们的值放在一个ByreBuffer中,为了避免定义两个坐标形状,使用绘画list告诉opengl 管道怎么样绘制这些顶点。

这下面是个示例

public class Square {

    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    // number of coordinates per vertex in this array
    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 }; // order to draw vertices

    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);
    }
}
这个例子向你展示如果使用opengl创建更复杂的图像,通常来说,你通过大量的三角形绘图,下一篇中,你将会学会如果在屏幕上绘制这些图像。


第三章 绘制图像

你用OpenGL定义完成之后,你应该绘制它们。使用opengl绘制可能比你想的需要写更多的代码。因为api给予了你对于渲染管道更强的更细致的控制。

这篇文章讲述怎么使用opengl 2使用之前已经定义好的形状进行绘制图像。

1. 初始化图形

在你在进行任何绘制操作之前,你必须初始化和加载你需要绘制的图形。

你应该在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();
    }
    ...
}

2. 绘制图形

绘制你定义好的图形还是需要很多的代码,因为你需要向图形渲染管道说明很多的细节。特别的,你必须定义下面这些:

Vertex Shader() -- opengl 渲染图形的定点

Fragment Shader -- opengl 用来渲染图像的表面,一般使用颜色或者纹理

Program -- opengl对象,它里面含有你绘制的图像。

你至少需要一个vertex shader去绘制图像的图形,你需要一个fragment shader去渲染图像的颜色。

这些shader必须被编译和加载进入opengl程序中,这个程序会使用它们来绘制图形。

这里有一个例子定义了基本的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;" +
        "}";

    ...
}
在opengl 环境中shader包含glsl语句的必须在使用它前被编译。

为了编译上面的代码,你需要在你的渲染类中创建一个工具方法。

public static int loadShader(int type, String shaderCode){

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // add the source code to the shader and compile it
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}
为了绘制你的图形,你必须编译shader code ,把他们加入到opengl es 对象中,然后把它们链接到程序中,在你图形构造函数中完成,所以这些操作只需要做一次。

到现在,你已经准备好将你增加真正的调用开始绘制你的图形,绘制opengl图像需要你指定几个参数告诉渲染管道你想绘制什么和你怎么绘制它。

因为绘制选项根据图形来说有多种多样,你可以让你的图形类包含自己的绘制方法。

在你的图形类中创建一个绘制的方法,下面的代码,设置了vertex shader的位置会fragment shader的颜色,然后执行绘制函数。

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() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // get handle to vertex shader's vPosition member
    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(mPositionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // get handle to fragment shader's vColor member
    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // Set color for drawing the triangle
    GLES20.glUniform4fv(mColorHandle, 1, color, 0);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}
这个三角形没有显示出来报错,有点心灰意冷,
还有三个小节做不出来,opengl技术有点难度,所以还是日后需要的时候再做打算,赶紧完成现在的部分才是真理。


小面几个小节的标题标注上

只是将背景设置成绿色成功,后面还有很多的内容都没有做。

加油

Applying Projection and Camera

Adding Motion

Responding to Touch Events













你可能感兴趣的:(learnandroid)