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

一妄壶、開始

OpenGl主要需要實現(xiàn)兩個類:

**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.

這個是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需要設置一個GLSurfaceView.Renderer的實現(xiàn)類去做OpenGl繪制的處理绿满。主要有三個方法:

從方法名字就可以看出來

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

二厅目、繪制簡單的撞球圖面

1.總覽

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

效果圖.png

首先我們思考下我們怎么去畫圖妖碉,我們需要一只筆猴仑,需要知道在怎么地方畫什么审轮,所以我們的使用OpenGL去繪制的大概步驟如下:

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

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

首先我們需要知道一個概念肥哎,OpenGL的坐標系是從-1到1的辽俗,如下圖:

OpenGL坐標系.png

然后我們還要了解到OpenGL只能繪制三種圖形,點篡诽、線崖飘、三角形,所以我們是不能直接畫出來一個矩形的杈女,但是呢朱浴,我們可以用兩個三角形去拼成一個矩形。
所以我們可以這樣定義我們的數(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,

            //點
            0f , 0.25f,
            0f , -0.25f
    };

我們如果用Java的話可以這樣去實現(xiàn)达椰,但是現(xiàn)在我們又需要去知道一個知識點了翰蠢,OpenGL是拿不到虛擬機里面的數(shù)據(jù)的,OpenGL只能使用Native的數(shù)據(jù)啰劲,所以現(xiàn)在我們需要找個辦法去讓OpenGL能夠使用我們定義的數(shù)據(jù)梁沧,我們可以這樣去做:
1.定義常量
private static final int BYTE_PRE_FLOAT = 4;
2.定義全局變量
private final FloatBuffer mVertexData;
3.在構造方法中添加如下代碼

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去實現(xiàn)的,而Shader分為兩類:
1.vertex shader:主要告訴GPU哪個點要畫在哪里栓辜,就是獲得點繪制的位置
2.fragment shader:主要告訴GPU哪個點要用什么顏色去畫恋拍,就是獲得點繪制的顏色
現(xiàn)在我們就可以去寫代碼了,在res/下建個raw包來存放OpenGL的實現(xiàn)代碼藕甩,然后創(chuàng)建simple_vertex_shader.glsl文件施敢,實現(xiàn)如下代碼:

attribute vec4 a_Position;

void main() {
    gl_Position = a_Position;
}

我們通過a_Position來獲取位置傳給gl_Position,gl_Position就是OpenGL最后用來繪制的位置。這里解釋下vec4表示我們定義了一個向量狭莱,這個向量有4個參數(shù)僵娃,分別為(x,y,z,w),xyz為3D坐標贩毕,w后面的文章會講到悯许,默認值為1。
然后我們還需要創(chuàng)建一個simple_fragment_shader.glsl文件辉阶,實現(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垃僚。我們有三個值可以選擇lowp集绰,mediumphighp谆棺,分別表示低清晰度栽燕,中等清晰度,高清晰度改淑,vertex shader對清晰度要求比較高碍岔,默認設置為highp,所以我們不用設置朵夏。
這次我們用了uniform蔼啦,在vertex shader我們使用的是attributeattribute對于每個點都是不同的仰猖,每個點都有自己的值捏肢,但是uniform的值是不變的,除非我們再次改變它饥侵。
這個我們依然用了vec4表示我們聲明了一個4個參數(shù)的向量鸵赫,只不過這次內容不一樣了,是(r,g,b,a)躏升,應該都猜到了辩棒,就是紅、綠煮甥、藍盗温、透明度。
這樣我們就完成了OpenGl部分代碼的編寫成肘,并對OpenGL有了一定的理解卖局。
現(xiàn)在問題來了,我們要怎么用這兩個東西去繪制到View上呢双霍?現(xiàn)在就讓我們去解決這個問題砚偶,大概需要五個步驟:
1.從.glsl文件讀出代碼內容
2.編譯讀出來的代碼
3.鏈接到程序
4.獲取參數(shù)的位置
5.使用獲取到的參數(shù)的位置去繪制
現(xiàn)在就讓我們一步一步去實現(xiàn)吧。

  1. .glsl文件讀出代碼內容
    可能很多地方都會用到讀取文件內容的功能洒闸,所以我們把該功能抽出來做一個工具類染坯,建一個util包,在該包下建一個TextResouceReader.java類丘逸,并實現(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. 編譯讀出來的代碼
    我們在util包下創(chuàng)建一個ShaderHelper.java類,然后實現(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)建了一個shader對象深纲,type有兩個值GL_VERTEX_SHADER,GL_FRAGMENT_SHADER仲锄,返回了一個int型劲妙,這個值是我們OpeGL對象的引用,當我們以后需要應用的時候儒喊,就要用這個值镣奋,如果返回0則表示創(chuàng)建失敗了。然后我們就需要用glShaderSource()上傳代碼怀愧,glCompileShader()編譯代碼侨颈,最后,我們檢查了編譯的狀態(tài)芯义。
為了方便使用我們在ShaderHelper.java方法中再添加兩個方法:

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實現(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)建一個program對象,然后把vertexShader毕贼,fragmentShader傳給program温赔,最后鏈接程序蛤奢,檢查是否鏈接成功鬼癣。

  1. 獲取參數(shù)的位置
    在獲取參數(shù)位置之前我們需要使我們鏈接的程序生效,還需要在ShaderHelper.java實現(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;
    }

我們已經在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);
    }

在讓我們的程序生效后,我們就要開始使用它了痹屹,所以我們調用了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ù)的位置去繪制
    最后我們就開始去繪制了志衍,首先我們在Renderer的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ù)的第一個值開始讀取暖庄,然后我們用glVertexAttribPointer()方法去從vertexData中讀取數(shù)據(jù)賦值給a_Position,該方法的各參數(shù)解釋如下:
POSITION_COMPOENT_COUNT:表示每個向量讀取兩個值
GL_FLOAT:表示數(shù)據(jù)類型
false:該值只有在用int型值的時候為true
0:這個值只有在數(shù)據(jù)有多個參數(shù)的時候需要使用楼肪,后面會討論培廓,目前置為0
具體的大家可以自己去查下Api。
現(xiàn)在OpenGL就知道如何從vertexData中讀取a_Position的值了春叫,最后我們需要添加一行代碼glEnableVertexAttribArray(aPositionLocation);肩钠,去讓a_Position能夠被使用。
接下來我們就可以去繪制了暂殖,在onDrawFrame()中添加如下代碼:

//繪制兩個三角形
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);
//繪制點
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()來繪制,第一個參數(shù)表示要繪制什么呛每,第二個參數(shù)表示從之前載入的數(shù)據(jù)哪里開始讀取踩窖,第三個參數(shù)表示讀幾個值。
這個時候就算完成了我們的基本功能了晨横,但是運行之后洋腮,你會發(fā)現(xiàn)繪制的點不見了廉沮,其實只是太小了,看不到而已徐矩,在simple_vertex_shader.glsl文件中添加如下代碼:gl_PointSize = 10;再運行一遍滞时,就能看到我們想要的效果了。

項目代碼在這里:https://github.com/KevinKmoo/SimpleAirHockey

能力有限滤灯,自己讀書的學習所得坪稽,有錯誤請指導,輕虐鳞骤!
轉載請注明出處窒百。----by kmoo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市豫尽,隨后出現(xiàn)的幾起案子篙梢,更是在濱河造成了極大的恐慌,老刑警劉巖美旧,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渤滞,死亡現(xiàn)場離奇詭異,居然都是意外死亡榴嗅,警方通過查閱死者的電腦和手機妄呕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗽测,“玉大人绪励,你說我怎么就攤上這事∵胫啵” “怎么了疏魏?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長晤愧。 經常有香客問我大莫,道長,這世上最難降的妖魔是什么养涮? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任葵硕,我火速辦了婚禮,結果婚禮上贯吓,老公的妹妹穿的比我還像新娘懈凹。我一直安慰自己,他們只是感情好悄谐,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布介评。 她就那樣靜靜地躺著,像睡著了一般渔隶。 火紅的嫁衣襯著肌膚如雪产捞。 梳的紋絲不亂的頭發(fā)上藕届,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天履因,我揣著相機與錄音,去河邊找鬼台猴。 笑死森渐,一個胖子當著我的面吹牛趾娃,可吹牛的內容都是我干的椅文。 我是一名探鬼主播喂很,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼皆刺!你這毒婦竟也來了少辣?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤羡蛾,失蹤者是張志新(化名)和其女友劉穎漓帅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痴怨,經...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡忙干,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了腿箩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豪直。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖珠移,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情末融,我是刑警寧澤钧惧,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站勾习,受9級特大地震影響浓瞪,放射性物質發(fā)生泄漏。R本人自食惡果不足惜巧婶,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一乾颁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧艺栈,春花似錦英岭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春吭狡,著一層夾襖步出監(jiān)牢的瞬間尖殃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工划煮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留送丰,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓弛秋,卻偏偏與公主長得像蚪战,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铐懊,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容