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

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