Android OpenGL 基礎入門

KingsLanding發表於2015-05-18

  Android 自從2.2 版本之後之後開始支援OpenGL,在沒有支援OpenGL 的 GPU的情況下,也可以使用(通過軟體來模擬)。在Android上使用Opengl操作的物件是GLSurfaceView,這是一個繼承自View的擴充套件。

  在Android上Opengl是通過Vertex Shader 和 Fragment Shader 這兩種定點著色器程式來實現圖片的載入和渲染的,中文稱為定點著色器和片段著色器。一個完整的Opengl程式需要建立定點著色器和片段著色器並將他們Link起來組成一個完整的OpenGL程式。

  頂點著色器的作用是為每一個頂點生成座標,因此每個頂點都要執行一遍頂點著色器程式,一旦頂點座標計算出來之後,OpenGL就能夠使用這些頂點來組成點,線,和三角形。所有任意的圖形都是由這三種基本元素來描述的。下圖是頂點著色器進行座標轉換的過程(稍微有點複雜):

  

  這個過程包含了從原始的物件座標經過模型檢視轉換生成眼座標,再經過投影轉換生成裁剪座標,再通過w座標的歸一化轉換成為NDC(頂點座標由(x,y,z,w)構成,在shader程式中一般用一個四維向量vec4來描述),最終通過視口變換生成螢幕座標顯示在螢幕上。這一系列的轉換都是通過矩陣來完成的,轉換過程和原理是Opengl的精華內容,對於需要進入3D世界的同學需要掌握。在2D世界中我們只需要瞭解NDC就行了,這裡就把NDC叫做OpenGL 座標,加上Normalized 的修飾是因為這些座標的值都在(-1,1)之間,OpenGL座標表示見下圖:

  OpenGL座標原點在螢幕中央,左右座標範圍為[-1,1],在2d環境中座標值只存在(x,y),並且座標範圍都只能在-1 到 1之間,螢幕中心座標為(0,0)。因此如果需要指定一張圖片的顯示位置,指定座標需要根據這個座標系來,此外為了保證圖片顯示比例(長寬比例),在portrait 到 landscape 之間變換的時候一般需要乘以一個aspectRatio (width / height) 來重新設定座標值。

  瞭解了Opengl座標,在來了解一下螢幕座標(螢幕座標的座標原點在左上角)和紋理座標(紋理座標的座標原點在左下角):

螢幕座標系                

  紋理座標系

  片段著色器的作用是為點,線或者三角形的每一個頂點的片段(Fragment)生成渲染後的最終顏色。片段就是一個小的單色矩形區域,可以簡單的認為是螢幕上的一個畫素點。

  以上基本知識基本上可以處理Opengl實現2D影象的繪製和處理。下面來簡單看一下Shader的寫法,在Android平臺上Shader程式一般以字串的形式出現,或者在res/raw/目錄下以*.glsl的格式出現,如果是以單獨的檔案出現,需要定義專門的檔案讀入介面來載入Shader程式。這裡就介紹一下字串的形式的Shader程式,來看下面簡單的Vertex Shader 和 Fragment Shader:

    private static final String VERTEX_SHADER =
        "attribute vec4 a_position;\n" +
        "attribute vec2 a_texcoord;\n" +
        "varying vec2 v_texcoord;\n" +
        "void main() {\n" +
        "  gl_Position = a_position;\n" +
        "  v_texcoord = a_texcoord;\n" +
        "}\n";

    private static final String FRAGMENT_SHADER =
        "precision mediump float;\n" +
        "uniform sampler2D tex_sampler;\n" +
        "varying vec2 v_texcoord;\n" +
        "void main() {\n" +
        "  gl_FragColor = texture2D(tex_sampler, v_texcoord);\n" +
        "}\n";

  先看一下VERTEX_SHADER,定義了兩種型別的變數 atrribute 和 varying。

  其中 attribute變數是隻能在vertex shader中使用的變數。(它不能在fragment shader中宣告attribute變數,也不能被fragment shader中使用),一般用attribute變數來表示一些頂點的資料,這些頂點資料包含了頂點座標,法線,紋理座標,頂點顏色等。應用中一般用函式glBindAttribLocation()來繫結每個attribute變數的位置,然後用函式glVertexAttribPointer()為每個attribute變數賦值。

  varying被稱為易變數,一般用於從Vertex Shader 向 Fragment Shader傳遞資料,上面例子中在VertexShader中定義了attribute 型別的二維向量a_texcoord,並將該值賦值給varying型別的二維向量 v_texcoord。此外對於Vertex Shader 在main() 中必須將頂點座標賦值給系統變數gl_Position。

  看一下FRAGMENT_SHADER,定義了兩種型別的變數,uniform 和 varying。此外還多了一句 precision mediump float,這句話用於定義資料精度,Opengl中可以設定三種型別的精度(lowp,medium 和 highp),對於Vertex Shader來說,Opengl使用的是預設最高精度級別(highp),因此沒有定義。

  uniform變數是APP序傳遞給(vertex和fragment)shader的變數。通過函式glUniform**()函式賦值的。 在(vertex和fragment)shader程式內部,uniform變數就像是C語言裡面的常量(const ),它不能被shader程式修改(shader只能用,不能改)。如果uniform變數在vertex和fragment兩者之間宣告方式完全一樣,則它可以在vertex和fragment共享使用。 (相當於一個被vertex和fragment shader共享的全域性變數)uniform變數一般用來表示:變換矩陣,材質,光照引數和顏色等資訊。

  對於Fragment Shader也需對gl_FragColor賦值。

  現在對基本的Shader有了瞭解,來看一下Android怎麼使用Shader程式。先說明一下Opengl中的資源一般都是用一個控制程式碼(handle)來引用,控制程式碼一般由gl***介面返回,代表一個特定的資源。

  在Android上使用gl,需要用到一些列介面,這裡按照一般的呼叫順序來列一下基本的介面(暫不包含錯誤處理)

  1. 和建立Shader相關的:glCreateShader -> glShaderSource -> glCompileShader , 通過這幾個介面的呼叫最終返回一個Shader的控制程式碼

  2. 和建立Shader程式相關的(每個shader程式都必須包含Vertex Shader 和 Fragment Shader兩部分):glCreateProgram -> glAttachShader -> glLinkProgram,通過這些介面的呼叫最終返回個Program的控制程式碼。

  3. 獲取Shader內部變數賦值和傳遞資料到gl,對於不同型別的資料使用不同的介面:GLES20.glGetUniformLocation(mProgram, "tex_sampler"),GLES20.glGetAttribLocation(mProgram, "a_texcoord"),以上兩個介面中mProgram 代表glCreateProgram返回的 Shader程式控制程式碼,“a_texcoord”和"tex_sampler"是 Vertex Shader和 Fragment Shader中定義的變數,通過這兩個介面獲取了變數的控制程式碼,便於向這些變數中傳入值。獲得變數的控制程式碼之後就要向其中傳值,一般通過glVertexAttribPointer()來完成,詳細引數這裡沒有列出,在例項程式碼中可以學習一下,傳值之後需要使glEnableVertexAttribArray才能將資料傳入到gl中,傳入資料使用glDrawArrays() 。

  這裡介紹了Opengl的基本知識,下一篇結合例項介紹一下Opengl在Android上的使用。

相關文章