Android OpenGL ES(一)----必備知識
1.手機的座標空間
我們都知道要想在手機上隨心所欲的繪製圖形,就必須瞭解手機的座標體系。下圖就是將座標對映到手機螢幕的座標。
圖1手機螢幕基本座標系
2.OpenGL基本圖形
在OpenGL裡,只能繪製點,直線以及三角形。
三角形是最基本的圖形,因為它的結構如此穩定,它隨處可見,比如橋樑的結構化構件,它有三條邊用來連線它的三個頂點,如果我們拿掉其中一個頂點,剩下的就是一條直線,如果我們再拿掉一個點,就只剩下一個點了。
點和直線可以用於某些效果,但是隻有三角形才能用來構造擁有複雜的物件和紋理的場景。在OpenGL裡,我們把單獨的點放在一個組裡構建出三角形,再告訴OpenGL如何連線這些點。我們想要構建的所有東西都要用點,直線和三角形定義,如果想構建更復雜的圖形,例如拱形,那我們就需要用足夠的點擬合這樣的曲線。
3.使資料可以被OpenGL存取
當我們在模擬器或者裝置上編譯和執行Java程式碼的時候,它並不是直接執行在硬體上的,相反,它執行在一個特殊的環境上,即Dalvik虛擬機器。執行在虛擬機器上的程式碼不能直接訪問本地環境,除非通過特定的API。
Dalvik虛擬機器還使用了垃圾回收機制。這意味著,當虛擬機器檢測到一個變數,物件或者其他記憶體片段不在被使用時,就會這些記憶體釋放掉以備重用,它也能騰挪記憶體以提高空間使用效率。
本地環境並不是這樣工作的,它不期望記憶體塊會被移來移去或者被自動釋放。
Android之所以這樣設計,是因為開發者在開發程式的時候不必關心特定的CPU或者機器架構,也不必關心底層的記憶體管理。這通常都能工作得很好,除非要與本地系統互動,必須OpenGL。OpenGL作為本地系統庫直接執行在硬體上,沒有虛擬機器,也沒有垃圾回收或記憶體壓縮。
Dalvik方案是Android主要特點之一,但是,如果程式碼執行在虛擬機器內部,那它怎麼與OpenGL通訊呢?有兩種技術,第一種技術是使用Java本地介面JNI,這個技術已經由Android軟體開發部提供,當呼叫android.opengl.GLES20包裡方法時,軟體開發包實際上就是在後臺使用JNI呼叫本地系統庫。
第二種技術就是改變記憶體分配的方式,Java有一個特殊的類集合,它們可以分配本地記憶體塊,並且把Java資料複製到本地記憶體。本地記憶體可以被本地環境存取,而不受垃圾回收器的管理。
圖2 從Dalvik到OpenGL傳輸資料
示例:
private float[] rectangle={
-0.5f,-0.5f,
0.5f,0.5f,
-0.5f,0.5f,
-0.5f,-0.5f,
0.5f,-0.5f,
0.5f,0.5f
}
private static final int BYTES_PER_FLOAT=4;
private final FloatBuffer vertexData;
vertexData=ByteBuffer
.allocateDirect(rectangle.length*BYTES_PER_FLOAT)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexData.put(rectangle);
這裡加入一個整型常量和一個FloatBuffer型別變數,一個java的浮點數有32位精度,而一個位元組只有8位精度,這點可能看起來很明顯,每個浮點數都佔4個位元組,而FloatBuffer用來在本地記憶體儲存資料。
首先,我們使用ByteBuffer.allocateDirect()分配了一塊本地記憶體,這塊記憶體不會被垃圾回收器管理。這個方法需要知道要分配多少個位元組的記憶體塊,因為頂點都儲存在一個浮點數組裡,並且每個浮點數有4個位元組,所以這塊記憶體的大小應該是rectangle.length*BYTES_PER_FLOAT。
下一行告訴位元組緩衝區按照本地位元組序組織它的內容。本地位元組序是指,當一個值佔用多個位元組時,比如32位整數,位元組按照從最重要位到最不重要位或者相反順序排列。可以認為這與從左到右或者從右到左寫一個數類似。知道這個排序並不重要,重要的是作為一個平臺要使用相同的排序,呼叫order(ByteOrder.nativeOrder())可以保證這一點。
最後,我們不願意直接操作單獨的位元組,而是希望使用浮點數,因此,呼叫asFloatBuffer()得到一個可以反映底層位元組的FloatBuffer類例項。然後就可以呼叫vertexData.put(rectangle)把資料從Dalvik的記憶體複製到本地記憶體了。當程序結束時,這塊記憶體會被釋放掉,所以,我們一般情況下不用關心它。但是,如果你在編寫程式碼的時候,建立了很多ByteBuffer,或者隨著程式執行產生了很多 ByteBuffer,你也許想學習一些碎片化以及記憶體管理的技術。
4.引入OpenGL管道
把圖形畫到螢幕上之前,它需要在OpenGL管道中傳遞,這就需要使用稱為著色器,這些著色器會告訴圖形處理單元如何繪製資料。有兩種型別的著色器,在繪製任何內容到螢幕之前,需要定義它們。
頂點著色器:生成每個頂點的最終位置,針對每個頂點,它都會執行一次,一旦最終位置確定了,OpenGL就可以這些可見頂點的集合組裝成點,直線以及三角形。
片段著色器:為組成點,直線或者三角形的每個片段生成最終的顏色,針對每個片段,它都會執行一次,一個片段是一個小小的,單一的顏色的長方形區域,類似於計算機螢幕上的一個畫素。
一旦最後的顏色生成了,OpenGL就會把它們寫到一塊稱為幀緩衝區的記憶體塊中,然後,Android會把這個幀緩衝區顯示到螢幕上。
圖3 OpenGL管道概述
5.建立頂點著色器
在Android專案中建立raw檔案,把著色器放入該資料夾下,便於引用。
示例simple_vertex_shader.glsl:
attribute vec4 a_Position;
void main()
{
gl_Position=a_Position;
}
這些著色器使用GLSL定義,GLSL是OpenGL的著色語言;這個著色語言的語法結構與C語言相似。
對於我們定義過的每個單一的定點,頂點著色器都會被呼叫一次;當它被呼叫的時候,它會在a_Position屬性裡接收當前頂點的位置,這個屬性被定義為vec4型別。
一個vec4是包含了4個分量的向量;在位置的上下文中,可以認為這4個分量是X,Y,Z和W座標,X,Y和Z對應一個三維位置,而W是一個特殊的座標,後面會專門講解W座標,現在暫時略過。如果沒有指定,預設情況下,OpenGL都是把向量的前三個座標設為0,並把最後一個座標設為1。
一個頂點會有幾個屬性,比如顏色和位置。關鍵詞"attribute"就是把這些屬性放進著色器的手段。
之後,可以定義main(),這是著色器的主要入口點;它所做的就是把前面定義過的位置複製到指定的輸出變數gl_Position;這個著色器一定要給gl_Position賦值;OpenGL會把gl_Position中儲存的位置當作頂點的最終位置,並把這些頂點組裝成點,直線和三角形。
6.建立片段著色器
光柵化技術
移動裝置的顯示屏是由成千上萬個小的,獨立的部件組成,它們被稱為畫素;這些畫素中的每一個都有能力顯示幾百萬種不同顏色範圍中的一種。然而,這實際上是一種視覺技巧:大多數顯示器並不能真正創造幾百萬種顏色,所以每個畫素通常是由三個單獨的子構建構成的,它們發出紅色,綠色,和藍色的光,因為每個畫素都非常小,人的眼睛會把紅色,綠色和藍色的光混合在一起,從而創造出巨量的顏色範圍;把足夠多的單獨的畫素放在一起,就能顯示出多種顏色。
OpenGL通過“光柵化”的過程把每個點,直線及三角形分解成大量的小片段,它們可以對映到移動裝置顯示屏上,從而生成一幅影象。這些片段類似於顯示屏上的畫素,每個都包含單一的純色。為了表示顏色,每個片段都有4個分量:其中紅色,綠色,藍色用來表示顏色,阿爾法分量用來表示透明度,
圖4光柵化:生成片段
編寫程式碼示例simple_fragment_shader.glsl:
precision mediump float;
uniform vec4 u_Color;
void main()
{
gl_FragColor=u_Color;
}
這個片段著色器中,檔案頂部的第一行程式碼定義了所有浮點資料型別的預設精度。這就像Java程式碼中選擇浮點數還是雙精度浮點數一樣。
可以選擇lowp,mediump,highp,它們分別對應低精度,中精度及高精度;然而,只有某些硬體實現支援在片段著色器中使用highp。
細心閱讀的可以發現為什麼頂點著色器沒有定義精度,其實頂點著色器同樣也可以定義精度,但是對於一個頂點而言,精確度是最重要的,OpenGL設計者決定把頂點著色器的精度預設設定成最高階-highp。
你可能已經猜到了,高精度資料型別更加準確,但是這是以降低效能為代價的;對於片段著色器,出於最大相容性的考慮,選擇了mediump,這也是基於速度和質量的權衡。
這個片段著色器的剩餘部分與早前定義的頂點著色器一樣。不過這次我們要傳遞一個uniform,它叫做u_Color。它不像屬性,每個頂點都要設定一個;一個uniform會讓每個頂點都使用同一個值,除非我們在次改變它。如定點著色器中的位置所使用的屬性一樣,u_Color也是一個四分量向量,但是在顏色的上下文中,這四分量分別對應紅色,綠色,藍色和阿爾法。
接著我們定義了main(),它是這個著色器的住入口點,它把我們在uniform裡定義的顏色複製到那個特殊的輸出變數---gl_FragColor。著色器一定要給gl_FragColor賦值,OpenGL會使用這個顏色作為當前片段的最終顏色。
記住一個句話就完全瞭解片段著色器:片段著色器的主要目的就是告訴GPU每個片段的最終顏色應該是什麼。
記住一個句話就完全瞭解頂點著色器:頂點著色器的主要目的就是確定每個頂點的最終位置。