Android OpenGl 介绍(一)

  • Post author:
  • Post category:其他



一、OpenGl 介绍


看到这个介绍,相信大家都不会陌生,因为在平时的工作中,或多或少大家都会听说过 openGl 这个东西,而且对它的印象基本都是觉得比较高深难懂。其实这个东西也不是那么难,那么无从下手,首先必须要了解一些基本的背景和搞懂一些基本概念,然后就可以按需学习了。


首先我们来看看官方对 OpenGl 的描述为:OpenGL( Open Graphics Library 开发图形接口)是一个跨平台的图形 API,用于指定 3D 图形处理硬件中的标准软件接口。


总结的来讲,OpenGl 提供了指定图形处理的硬件接口,我们在处理图形的时候,只要按照它的规则来调用,就可以获得更加高效的图形处理方法。


为了承上启下,必须要再了解以下 OpenGl 的背景,它的前身是 SGI 公司为其图形工作站开发的 IRIS GL。由于 IRIS GL 的兼容性不好,而且不太容易移植。因此在其基础上,SGI 公司开发出了 OpenGl。OpenGl 一般用于PC端的图形工作站,在移动端使用 OpenGl 基本带不动。为此,Khronos 公司就为 OpenGl 提供了一个子集,OpenGl ES(OpenGl for Embedded System)。是不是很自然的知道 OpenGl ES是啥,接着往下看。


二、OpenGl ES 介绍


这里直接上官方解释:OpenGl ES是免费的跨平台的功能完善的 2D/3D 图形库接口的 API,是OpenGL 的一个子集。


移动端使用到的基本上都是 OpenGl ES,当然 Android 开发下还专门提供了android.opengl 包,并且提供了 GlSurfaceView,GLU,GlUtils 等工具类。相信介绍到这里,大家都知道 OpenGl ES 是干什么的,很不巧,这里又是一句承上启下的话,因为这里引出了一堆新的名词,别急,下面一一介绍,大家就可以连起来了。


三、GlSurfaceView 介绍


顾名思义,它是一个SurfaceView,看源码可知,GlSurfaceView 继承 SurfaceView 的同时,增加了 Renderer,作用就是专门为 OpenGl 显示渲染使用的。看看下面的使用方法,十分平易近人:以通过创建的实例使用这个类,并随心所欲的设计属于你的 Renderer。

GLSurfaceView glSurfaceView = new GLSurfaceView(this);
glSurfaceView.setRenderer(new GLSurfaceView.Renderer() {
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {

    }

    @Override
    public void onDrawFrame(GL10 gl) {

    }
});


很巧,这里突然又冒出了个 GlSurfaceView.Renderer。


四、GlSurfaceView.Renderer 介绍


GlSurfaceView.Renderer 定义了用于绘制在图形所需的方法。在使用的时候,必须提供这个接口作为一个单独的类的实现,并通过 GLSurfaceView.setRenderer() 将其连接到 GLSurfaceView 使用实例,如上面的代码所示。通过 @Override,在此接口中设计随心所欲的渲染方法,当然这些方法是需要按照 OpenGl 的规则的。下面介绍以下该接口的方法的作用:


  • onSurfaceCreated():系统会在 GLSurfaceView 调用这个方法,因此这个方法适合用来执行只需要发生一次的操作,比如设置 OpenGL 的环境参数或初始化 OpenGL 图形对象。

  • onDrawFrame():系统调用此方法来重新绘制 GLSurfaceView,因此这个方法主要是用于绘制(和重新绘制)图形对象。

  • onSurfaceChanged():GLSurfaceView 当几何形状发生变化的时候,包括尺寸变化、设备屏幕的旋转。例如,当设备从纵向变为横向。


讲到这里,相信大家都有个基本概念和使用流程了,这里总结一下:


  • new 一个 GlSurfaceView;

  • 为这个 GlSurfaceView 配置渲染相关的设置;

  • 在 GlSurfaceView.Renderer 中绘制处理显示数据。


说了这么多,来个例子吧,图文并茂。


五、绘制几何图形


1、创建正方型图的类:

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,   // 左上角
            -0.5f, -0.5f, 0.0f,   // 左下角
             0.5f, -0.5f, 0.0f,   // 右下角
             0.5f,  0.5f, 0.0f }; // 右上角
    // 绘制顶点的顺序
    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 };

    public Square() {
        // 初始化 ByteBuffer,长度为 arr 数组的长度 * 4,因为 float 占4个字节
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(squareCoords.length * 4);
        // 数组排列用 nativeOrder        
        byteBuffer.order(ByteOrder.nativeOrder());
        // 从 ByteBuffer 创建一个浮点缓冲区
        vertexBuffer = byteBuffer.asFloatBuffer();
        // 将坐标添加到 FloatBuffer
        vertexBuffer.put(squareCoords);
        // 设置缓冲区来读取第一个坐标
        vertexBuffer.position(0);

        // 初始化 ByteBuffer,长度为 arr 数组的长度 * 2,因为 short 占2个字节
        byteBuffer = ByteBuffer.allocateDirect(drawOrder.length * 2);
        byteBuffer.order(ByteOrder.nativeOrder());
        drawListBuffer = byteBuffer.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
    }
}


这里要注意的是 Java 和 OpenGl 的缓冲区数据存储结构存在差异,Java 为 Big-Edian,而 OpenGl 为 Little-Edian。因此在 Android 中使用 OpenGl 的时候,需要进行数据存储格式转换。在代码中,一般会封装成工具类,这里提供几个封装好的轮子。


(1)将 int[] 数组转为 OpenGl 需要的 IntBuffer

private IntBuffer intBufferUtil(int[] intArray)
{
    IntBuffer intBuffer;
    // 初始化 ByteBuffer,长度为 intArray 数组的长度 * 4(sizeof(int)这就是4的由来)
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(intArray.length * 4);
    // 数组排列用 nativeOrder
    byteBuffer.order(ByteOrder.nativeOrder());
    intBuffer = byteBuffer.asIntBuffer();
    intBuffer.put(intArray);
    intBuffer.position(0);
    return intBuffer;
}


(2)将 float[] 数组转为 OpenGl 需要的 FloatBuffer

private FloatBuffer floatBufferUtil(float[] floatArray)
{
    FloatBuffer floatBuffer;
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(floatArray.length * 4);
    byteBuffer.order(ByteOrder.nativeOrder());
    floatBuffer = byteBuffer.asFloatBuffer();
    floatBuffer.put(floatArray);
    floatBuffer.position(0);
    return floatArray;
}


(3)将 short[] 数组转为 OpenGl 需要的 ShortBuffer

private ShortBuffer shortBufferUtil(short[] shortArray)
{
    ShortBuffer shortBuffer;
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(shortArray.length * 2);
    byteBuffer.order(ByteOrder.nativeOrder());
    shortBuffer = byteBuffer.asShortBuffer();
    shortBuffer.put(shortArray);
    shortBuffer.position(0);
    return shortBuffer;
}


2、将形状渲染到 GlSurfaceView 中,主要可分为下面几步:

(1)首先我们需要在 GlSurfaceView.Renderer 中初始化需要渲染的几何图形

private Triangle mTriangle;
private Square mSquare;

public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    // 设置背景颜色
    GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    // 初始化 square
    mSquare = new Square();
}


(2)在绘制图形前至少需要一个顶点着色器来绘制形状和一个片段着色器的颜色。这些着色器要进行编译,然后加入到一个 OpenGL ES 程序。这里有两个比较难理解的概念:

  • 顶点着色器(Vertex Shader),也称为顶点遮蔽器,根据官方解释,顶点着色引擎是一种增加各式特效在3D场影中的处理单元,顶点着色引擎的可程式化特性允许开发者靠加载新的软件指令来调整各式的特效,每一个顶点将被各种的数据变素清楚地定义,至少包括每一顶点的x、y、z坐标,每一点顶点可能包函的数据有颜色、最初的径路、材质、光线特征等。前面的解释十分官方,简单点来说就是,你随心所欲的想法给到它,它来处理顶点(位置,颜色,纹理坐标),是用于渲染图形顶点的 OpenGL ES 图形代码。

  • 片段着色器(Fragment Shader ) ,也称为

    像素着色器,

    用于呈现形状的面的颜色或纹理的OpenGL ES代码。

  • 项目(Program),包含要用于绘制一个或多个形状着色器的 OpenGL ES 的对象。


下面给 Square 类定义基本的着色器代码:

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

...
}


注意:着色器的代码执行起来代价十分的高,因此多次执行是不行的,一般将执行代码的逻辑写在带图形类的构造方法中。比如上面的 Square:

private final int mProgram;
public Square() {

    ... ...//这里省略上面的代码

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

    // 创建空的OpenGL ES程序
    mProgram = GLES20.glCreateProgram();

    // 添加顶点着色器到程序中
    GLES20.glAttachShader(mProgram, vertexShader);

    // 添加片段着色器到程序中
    GLES20.glAttachShader(mProgram, fragmentShader);

    // 创建OpenGL ES程序可执行文件
    GLES20.glLinkProgram(mProgram);
}

到这里,所有绘制的基本配置都完成了,然后就可以开始愉快的写绘制图形的方法,在形状类(Square)中创建一个绘制的方法 onDraw(),可以在 onDraw() 方法中编写绘制的逻辑。

public Square() {

    ... ...//这里省略上面的代码


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

        // 获取顶点着色器的位置的句柄
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // 启用三角形顶点位置的句柄
        GLES20.glEnableVertexAttribArray(mPositionHandle);

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

        // 获取片段着色器的颜色的句柄
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

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

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

        // 禁用顶点数组
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}

完成上面所有步骤,只需要在GlSurfaceView.Renderer的onDrawFrame()方法中调用图形类的绘制方法即可(上面的onDraw()):

@Override
public void onDrawFrame(GL10 unused) {
    mSquare.draw();
}

大功告成!!!


参考链接:



版权声明:本文为2301_76382363原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。