Android OpenGl学习(一)

最近由于项目需要,所以开始学习OpenGL,网络上的东西零零散散,所以就想写一系列博客来记录学习OpenGL。

一、简介

首先我们要对其有一个简单的认识

什么是OpenGL?

官方描述:OpenGL是一个跨平台的图形API,用于指定3D图形处理硬件中的标准软件接口。

OpenGL的优势?

OpenGl是用来做图像处理的,那我们为什么不使用Canvas呢?费劲学这个干嘛?

答案很简单,为了效率,使用Canvas画图使用的是CPU,那我们都知道手机上还有一个专门的图形处理单元叫GPU,它可以并行的做浮点运算,OpenGL使用的就是GPU,这样的话可以用GPU分担CPU的工作量,提高图形渲染效率。

概念

顶点着色器:它的作用就是为每一个顶点生成座标,因此每个顶点都要运行一遍顶点着色器程序,一旦顶点座标计算出来之后,OpenGL就能够使用这些顶点来组成点,线,面。

片段着色器:它的作用就是为每一个顶点的片段渲染颜色。

座标系:原点在中心,范围为[-1,1],2d环境只有(x,y),3d环境有(x,y,z)

二、画个三角形

1.创建GLSurfaceView

了解了基础概念之后我们就开始上代码,要使用OpenGL进行绘制就必须要有一个地方来供它绘制,它既然是使用GPU进行计算的,那这块地方就肯定与Canvas有所不同,这个地方就是GLSurfaceView,它是android为我们提供的一个类,继承于SurfaceView,也就是说它管理着一块Surface。

要创建SurfaceView可以使用new来创建,也可以在xml中书写

GLSurfaceView glSurfaceView=new GLSurfaceView(context);
setContentView(glSurfaceView);

或者

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.opengl.GLSurfaceView
        android:id="@+id/glSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
GLSurfaceView glSurfaceView = (GLSurfaceView) findViewById(R.id.glSurfaceView);

创建完成后还要为其设定要使用的OpenGL版本,一般来说我们为其设置版本好为2

glSurfaceView.setEGLContextClientVersion(2);

为其设置Render,这个才是控制渲染的地方

glSurfaceView.setRenderer(renderer);

2.书写Render

Render是一个接口,存在于GLSurfaceView类中,你需要将它里面的三个重要的方法实现

    public interface Renderer {
        //当GlSurfaceView创建时被调用
        void onSurfaceCreated(GL10 var1, EGLConfig var2);
        //当GlSurfaceView尺寸发生变化时被调用
        void onSurfaceChanged(GL10 var1, int var2, int var3);
        //当画每一帧的时候都会被调用
        void onDrawFrame(GL10 var1);
    }

一般来说我们在onSurfaceCreated中绘制背景,在onSurfaceChanged中设置视图大小,在onDrawFrame中进行真正的绘画,不过要在绘画前清空视图,下面给一个简单的示例:

    public class MyRender implements GLSurfaceView.Renderer {

        @Override
        public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
            GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);
        }

        @Override
        public void onSurfaceChanged(GL10 gl10, int width, int height) {
            GLES20.glViewport(0,0,width,height);
        }

        @Override
        public void onDrawFrame(GL10 gl10) {
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
        }
    }

该Render绘制出来就是一个灰色的背景,上面没有任何图形,要绘制图形的话还要使用Shader,比如说我们要绘制三角形,那么它的ShaderCode就应该这么写

private final String vertexShaderCode=
        "attribute vec4 vPosition;"+
                "void main(){"+
                "   gl_Position=vPosition;"+
                "}";

当然还要给图形上色了,涂颜色也需要使用ShaderCode

private final String fragmentShaderCode=
        "precision mediump float;"+
                "uniform vec4 vColor;"+
                "void main(){"+
                "   gl_FragColor=vColor;"+
                "}";

现在你先不用着急去研究Shader该怎么写,先熟悉整个流程

现在有了画图的程序和上色的程序还缺什么呢,缺少座标,你不给座标程序怎么知道你要画在哪,怎么画

//用于装载座标点
private FloatBuffer vertexBuffer;
//编译ShaderCode后程序的指针
private int mProgram;
//一个座标要用三个数表示,x,y,z
private final int COORDS_PER_VERTEX=3;
//座标数组
private float triangleCoords[]={
        0.5f,0.5f,0.0f,
        -0.5f,-0.5f,0.0f,
        0.5f,-0.5f,0.0f
};
//顶点句柄
private int mPositionHandle;
//颜色句柄
private int mColorHandle;
//座标点数量
private final int vertexCount=triangleCoords.length/COORDS_PER_VERTEX;
//一个float占四位,一共占座标点数量*4位的空间
private final int vertexStride=COORDS_PER_VERTEX*4;

private float[] color={1.0f,1.0f,1.0f,1.0f};

接下来就是真正的编译程序和绘画了

@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
    //设置背景
    GLES20.glClearColor(0.5f,0.5f,0.5f,1.0f);
    //初始化ByteBuffer,为其分配空间
    ByteBuffer byteBuffer=ByteBuffer.allocateDirect(triangleCoords.length*4);
    //设置排列顺序为nativeOrder
    byteBuffer.order(ByteOrder.nativeOrder());
    //将ByteBuffer转换为FloatBuffer
    vertexBuffer=byteBuffer.asFloatBuffer();
    //将座标点放入FloatBuffer
    vertexBuffer.put(triangleCoords);
    //设置起点
    vertexBuffer.position(0);
    //获得编译后的顶点程序句柄
    int vertexShader=loadShader(GLES20.GL_VERTEX_SHADER,vertexShaderCode);
    //获得编译后的颜色程序句柄
    int fragmentShader=loadShader(GLES20.GL_FRAGMENT_SHADER,fragmentShaderCode);
    //创建一个OpenGLES程序
    mProgram=GLES20.glCreateProgram();
    //将编译后的顶点程序加入其中
    GLES20.glAttachShader(mProgram,vertexShader);
    //将编译后的颜色程序加入其中
    GLES20.glAttachShader(mProgram,fragmentShader);
    //连接程序
    GLES20.glLinkProgram(mProgram);
    //声明一个数组,用于存放连接程序结果
    int[] linkStatus = new int[1];
    //获取连接程序结果,将其存入linkStatus的第0个位置
    GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
    //对连接程序结果的处理
    if (linkStatus[0] != GLES20.GL_TRUE) {
        Log.e("ES20_ERROR", "Could not link program: ");
        Log.e("ES20_ERROR", GLES20.glGetProgramInfoLog(mProgram));
        GLES20.glDeleteProgram(mProgram);
        mProgram = 0;
    }
}

@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
    //设置视图大小
    GLES20.glViewport(0,0,width,height);
}

@Override
public void onDrawFrame(GL10 gl10) {
    //清空视图
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT|GLES20.GL_DEPTH_BUFFER_BIT);
    //将程序加入到OpenGLES环境
    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);
}

//根据类型加载ShaderCode程序
protected int loadShader(int type, String shaderCode){
    //根据类型创建着色器
    int shader= GLES20.glCreateShader(type);
    //加入代码
    GLES20.glShaderSource(shader,shaderCode);
    //开始编译
    GLES20.glCompileShader(shader);
    //声明一个数组,用于存放编译结果
    int[] compiled = new int[1];
    //获取编译结果,将其存入到compiled的第0个位置中
    GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
    //根据编译结果做处理
    if (compiled[0] == 0) {
        Log.e("ES20_ERROR", "Could not compile shader " + type + " : "+shaderCode);
        Log.e("ES20_ERROR", GLES20.glGetShaderInfoLog(shader));
        GLES20.glDeleteShader(shader);
        shader = 0;
    }
    return shader;
}

这样一个三角就画好了,但是你会发现画出来的三角形和填入的座标并不相符,明明应该是一个等腰直角三角形,为什么被拉长了呢?因为你的屏幕不是方的,你的屏幕要是方的它就是一个等腰直角三角形了,所以怎么才能把它转换一下呢?这就涉及到了投影和相机视图了,下篇我会讲一下。

项目地址:https://github.com/s15603333319/AndroidOpenGL

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章