OpenGl-ES2.0 For Android 讀書筆記(一)

一、開始

OpenGl主要需要實(shí)現(xiàn)兩個(gè)類:

**GLSurfaceView
**
This class is a View where you can draw and manipulate objects using OpenGL API calls and is similar in function to a SurfaceView. You can use this class by creating an instance of GLSurfaceView and adding your Renderer to it. However, if you want to capture touch screen events, you should extend the GLSurfaceView class to implement the touch listeners, as shown in OpenGL training lesson, Responding to Touch Events.

這個(gè)是Android官方的解釋,跟SurfaceView類似的可以用OpenGL去繪制的View。

**GLSurfaceView.Renderer
**
This interface defines the methods required for drawing graphics in a GLSurfaceView. You must provide an implementation of this interface as a separate class and attach it to your GLSurfaceView instance using GLSurfaceView.setRenderer().

GLSurfaceView需要設(shè)置一個(gè)GLSurfaceView.Renderer的實(shí)現(xiàn)類去做OpenGl繪制的處理。主要有三個(gè)方法:

從方法名字就可以看出來

  • onSurfaceCreated()主要做一些初始化的操作
  • onSurfaceChanged()主要做一些在view改變時(shí)候的處理
  • onDrawFrame()主要做繪制處理

二、繪制簡(jiǎn)單的撞球圖面

1.總覽

我們最終完成的效果如下圖:

效果圖.png

首先我們思考下我們?cè)趺慈ギ媹D,我們需要一只筆,需要知道在怎么地方畫什么,所以我們的使用OpenGL去繪制的大概步驟如下:

  1. 定義繪圖數(shù)據(jù)
  2. 告訴手機(jī)如何使用這些數(shù)據(jù)去繪制

2.定義繪圖數(shù)據(jù)

首先我們需要知道一個(gè)概念,OpenGL的坐標(biāo)系是從-1到1的,如下圖:

OpenGL坐標(biāo)系.png

然后我們還要了解到OpenGL只能繪制三種圖形,點(diǎn)、線、三角形,所以我們是不能直接畫出來一個(gè)矩形的,但是呢,我們可以用兩個(gè)三角形去拼成一個(gè)矩形。
所以我們可以這樣定義我們的數(shù)據(jù):

private float[] mData = new float[]{
            //三角形
            -0.5f , 0.5f,
            -0.5f , -0.5f,
            0.5f , 0.5f,

            -0.5f , 0.5f,
            0.5f , 0.5f,
            0.5f , -0.5f,

            //線
            -0.5f , 0f,
            0.5f , 0f,

            //點(diǎn)
            0f , 0.25f,
            0f , -0.25f
    };

我們?nèi)绻肑ava的話可以這樣去實(shí)現(xiàn),但是現(xiàn)在我們又需要去知道一個(gè)知識(shí)點(diǎn)了,OpenGL是拿不到虛擬機(jī)里面的數(shù)據(jù)的,OpenGL只能使用Native的數(shù)據(jù),所以現(xiàn)在我們需要找個(gè)辦法去讓OpenGL能夠使用我們定義的數(shù)據(jù),我們可以這樣去做:
1.定義常量
private static final int BYTE_PRE_FLOAT = 4;
2.定義全局變量
private final FloatBuffer mVertexData;
3.在構(gòu)造方法中添加如下代碼

mVertexData = ByteBuffer
                .allocateDirect(mData.length * BYTE_PRE_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
mVertexData.put(mData);

3.使用OpenGL繪制

現(xiàn)在我們有了數(shù)據(jù),而且能夠使用數(shù)據(jù)了,現(xiàn)在我們就可以開始去做繪制的工作了。
首先我們需要知道OpenGL的繪制是通過Shader去實(shí)現(xiàn)的,而Shader分為兩類:
1.vertex shader:主要告訴GPU哪個(gè)點(diǎn)要畫在哪里,就是獲得點(diǎn)繪制的位置
2.fragment shader:主要告訴GPU哪個(gè)點(diǎn)要用什么顏色去畫,就是獲得點(diǎn)繪制的顏色
現(xiàn)在我們就可以去寫代碼了,在res/下建個(gè)raw包來存放OpenGL的實(shí)現(xiàn)代碼,然后創(chuàng)建simple_vertex_shader.glsl文件,實(shí)現(xiàn)如下代碼:

attribute vec4 a_Position;

void main() {
    gl_Position = a_Position;
}

我們通過a_Position來獲取位置傳給gl_Position,gl_Position就是OpenGL最后用來繪制的位置。這里解釋下vec4表示我們定義了一個(gè)向量,這個(gè)向量有4個(gè)參數(shù),分別為(x,y,z,w),xyz為3D坐標(biāo),w后面的文章會(huì)講到,默認(rèn)值為1。
然后我們還需要?jiǎng)?chuàng)建一個(gè)simple_fragment_shader.glsl文件,實(shí)現(xiàn)如下代碼:

precision mediump float;

uniform vec4 u_Color;

void main() {
    gl_FragColor = u_Color;
}

類似simple_vertex_shader.glsl,我們也是通過u_Color獲取顏色傳遞給gl_FragColor,gl_FragColor就是最后要繪制的顏色。
第一行代碼確定所有float型的清晰度,就像是Java中double和float。我們有三個(gè)值可以選擇lowp,mediump,highp,分別表示低清晰度,中等清晰度,高清晰度,vertex shader對(duì)清晰度要求比較高,默認(rèn)設(shè)置為highp,所以我們不用設(shè)置。
這次我們用了uniform,在vertex shader我們使用的是attribute,attribute對(duì)于每個(gè)點(diǎn)都是不同的,每個(gè)點(diǎn)都有自己的值,但是uniform的值是不變的,除非我們?cè)俅胃淖兯?br> 這個(gè)我們依然用了vec4表示我們聲明了一個(gè)4個(gè)參數(shù)的向量,只不過這次內(nèi)容不一樣了,是(r,g,b,a),應(yīng)該都猜到了,就是紅、綠、藍(lán)、透明度。
這樣我們就完成了OpenGl部分代碼的編寫,并對(duì)OpenGL有了一定的理解。
現(xiàn)在問題來了,我們要怎么用這兩個(gè)東西去繪制到View上呢?現(xiàn)在就讓我們?nèi)ソ鉀Q這個(gè)問題,大概需要五個(gè)步驟:
1.從.glsl文件讀出代碼內(nèi)容
2.編譯讀出來的代碼
3.鏈接到程序
4.獲取參數(shù)的位置
5.使用獲取到的參數(shù)的位置去繪制
現(xiàn)在就讓我們一步一步去實(shí)現(xiàn)吧。

  1. .glsl文件讀出代碼內(nèi)容
    可能很多地方都會(huì)用到讀取文件內(nèi)容的功能,所以我們把該功能抽出來做一個(gè)工具類,建一個(gè)util包,在該包下建一個(gè)TextResouceReader.java類,并實(shí)現(xiàn)如下方法:
public static String readTextFileFromResource(Context context , int resourceId){

        StringBuilder body = new StringBuilder();

        InputStream inputStream = null;
        InputStreamReader reader = null;
        BufferedReader bufferedReader = null;
        try {
            inputStream = context.getResources().openRawResource(resourceId);
            reader = new InputStreamReader(inputStream);
            bufferedReader = new BufferedReader(reader);
            String line = null;
            while ((line = bufferedReader.readLine()) != null){
                body.append(line + "\n");
            }
        }catch (Exception e){
            Logger.debug(TAG , "read file has error");
        }finally {
            try {
                if (bufferedReader != null){
                    bufferedReader.close();
                }
                if (reader != null){
                    reader.close();
                }
                if (inputStream != null){
                    inputStream.close();
                }
            }catch (Exception e){}
        }
        return body.toString();
    }

然后我們第一步就可以完成了,在onSurfaceCreated()方法中讀取代碼

String vertexShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.sample_vertex_shader);
String fragmentShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.simple_fragment_shader);
  1. 編譯讀出來的代碼
    我們?cè)?code>util包下創(chuàng)建一個(gè)ShaderHelper.java類,然后實(shí)現(xiàn)如下方法
public static int compileShaderCode(int type ,  String sourceCode){
        final int shaderObjectId = glCreateShader(type);
        if (shaderObjectId == 0){
            Logger.debug(TAG , "can not create sahder");
            return 0;
        }
        glShaderSource(shaderObjectId , sourceCode);
        glCompileShader(shaderObjectId);
        final int[] compileStatus = new int[1];
        glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
        if (compileStatus[0] == 0){
            Logger.debug(TAG , "compile fail");           
        }
        return shaderObjectId;
    }

首先我們創(chuàng)建了一個(gè)shader對(duì)象,type有兩個(gè)值GL_VERTEX_SHADER,GL_FRAGMENT_SHADER,返回了一個(gè)int型,這個(gè)值是我們OpeGL對(duì)象的引用,當(dāng)我們以后需要應(yīng)用的時(shí)候,就要用這個(gè)值,如果返回0則表示創(chuàng)建失敗了。然后我們就需要用glShaderSource()上傳代碼,glCompileShader()編譯代碼,最后,我們檢查了編譯的狀態(tài)。
為了方便使用我們?cè)?code>ShaderHelper.java方法中再添加兩個(gè)方法:

public static int compileVertexShader(String sourceCode){
        return compileShaderCode(GL_VERTEX_SHADER , sourceCode);
    }
public static int compileFragmentShader(String sourceCode){
        return compileShaderCode(GL_FRAGMENT_SHADER , sourceCode);
    }
  1. 鏈接到程序
    接下來我們需要在ShaderHelper.java實(shí)現(xiàn)如下的方法:
public static int linkProgram(int vertexShaderId , int fragmentShaderId){
        int programId = glCreateProgram();

        if (programId == 0){
            Logger.debug(TAG , "create program fail");
            return 0;
        }
        glAttachShader(programId , vertexShaderId);
        glAttachShader(programId , fragmentShaderId);
        glLinkProgram(programId);

        final int[] linkStatus = new int[1];
        glGetProgramiv(programId , GL_LINK_STATUS , linkStatus , 0);
        if (linkStatus[0] == 0){
            Logger.debug(TAG , "link fail");
            return 0;
        }

        return programId;
    }

跟編譯代碼類似,我們先創(chuàng)建一個(gè)program對(duì)象,然后把vertexShader,fragmentShader傳給program,最后鏈接程序,檢查是否鏈接成功。

  1. 獲取參數(shù)的位置
    在獲取參數(shù)位置之前我們需要使我們鏈接的程序生效,還需要在ShaderHelper.java實(shí)現(xiàn)如下方法:
public static boolean vaildProgram(int programId){
        glValidateProgram(programId);

        final int[] validateStatus = new int[1];
        glGetProgramiv(programId, GL_VALIDATE_STATUS, validateStatus, 0);
        return validateStatus[0] != 0;
    }

我們已經(jīng)在ShaderHelper.java中完成了這些方法,我們現(xiàn)在可以方便的用這些方法在Renderder的onSurfaceCreated()方法中去使用這些方法了。

@Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        String vertexShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.sample_vertex_shader);
        String fragmentShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.simple_fragment_shader);

        int vertextShaderId = ShaderHelper.compileVertexShader(vertexShaderCode);
        int fragmentShaderId = ShaderHelper.compileFragmentShader(fragmentShaderCode);

        mProgram = ShaderHelper.linkProgram(vertextShaderId , fragmentShaderId);
        ShaderHelper.vaildProgram(mProgram);

        glUseProgram(mProgram);
    }

在讓我們的程序生效后,我們就要開始使用它了,所以我們調(diào)用了glUseProgram()方法。
接下來我們來獲取我們定義的u_Color的位置信息,我們先做如下聲明:

private static final String U_COLOR = "u_Color";
private int mUColorLocation;

然后在onSurfaceCreated()方法中獲取u_Color的位置信息
mUColorLocation = glGetUniformLocation(mProgram , U_COLOR);
類似的我們可以獲取a_Position的位置信息:

private static final String A_POSITION = "a_Position";
private int mAPositionLocation;

mAPositionLocation = glGetAttribLocation(mProgram , A_POSITION);

  1. 使用獲取到的參數(shù)的位置去繪制
    最后我們就開始去繪制了,首先我們?cè)赗enderer的onSurfaceCreated()方法中添加如下代碼:
mVertexData.position(0);
glVertexAttribPointer(mAPositionLocation , POSITION_COMPOENT_COUNT , GL_FLOAT , false , 0 , mVertexData);

POSITION_COMPOENT_COUNT為常量:
private static final int POSITION_COMPOENT_COUNT = 2;
mVertexData.position(0)讓我們可以保證從數(shù)據(jù)的第一個(gè)值開始讀取,然后我們用glVertexAttribPointer()方法去從vertexData中讀取數(shù)據(jù)賦值給a_Position,該方法的各參數(shù)解釋如下:
POSITION_COMPOENT_COUNT:表示每個(gè)向量讀取兩個(gè)值
GL_FLOAT:表示數(shù)據(jù)類型
false:該值只有在用int型值的時(shí)候?yàn)閠rue
0:這個(gè)值只有在數(shù)據(jù)有多個(gè)參數(shù)的時(shí)候需要使用,后面會(huì)討論,目前置為0
具體的大家可以自己去查下Api。
現(xiàn)在OpenGL就知道如何從vertexData中讀取a_Position的值了,最后我們需要添加一行代碼glEnableVertexAttribArray(aPositionLocation);,去讓a_Position能夠被使用。
接下來我們就可以去繪制了,在onDrawFrame()中添加如下代碼:

//繪制兩個(gè)三角形
glUniform4f(mUColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
glDrawArrays(GL_TRIANGLES, 0, 6);
//繪制線
glUniform4f(mUColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_LINES, 6, 2);
//繪制點(diǎn)
glUniform4f(mUColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
glDrawArrays(GL_POINTS, 8, 1);
glUniform4f(mUColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_POINTS, 9, 1);

glUniform4f()用來給u_Color賦值。
glDrawArrays()來繪制,第一個(gè)參數(shù)表示要繪制什么,第二個(gè)參數(shù)表示從之前載入的數(shù)據(jù)哪里開始讀取,第三個(gè)參數(shù)表示讀幾個(gè)值。
這個(gè)時(shí)候就算完成了我們的基本功能了,但是運(yùn)行之后,你會(huì)發(fā)現(xiàn)繪制的點(diǎn)不見了,其實(shí)只是太小了,看不到而已,在simple_vertex_shader.glsl文件中添加如下代碼:gl_PointSize = 10;再運(yùn)行一遍,就能看到我們想要的效果了。

項(xiàng)目代碼在這里:https://github.com/KevinKmoo/SimpleAirHockey

能力有限,自己讀書的學(xué)習(xí)所得,有錯(cuò)誤請(qǐng)指導(dǎo),輕虐!
轉(zhuǎn)載請(qǐng)注明出處。----by kmoo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容