這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
OpenGL ES(OpenGL for Embedded Systems)是 OpenGL 三維圖形API的子集,針對手機、PDA和遊戲主機等嵌入式裝置而設計。該API由Khronos集團定義推廣,Khronos是一個圖形軟硬體行業協會,該協會主要關注圖形和多媒體方面的開放標準。
go 的 golang.org/x/mobile/gl 這個包 是基於OpenGL ES 2了, 文檔在: https://godoc.org/golang.org/x/mobile/gl
Khronos的api文檔在https://www.khronos.org/opengles/sdk/docs/man/
以 gomobile 的例子中的 basic 例子(源碼在:https://github.com/golang/mobile/tree/master/example)為例,它的執行效果如下,
注意這是在不同長寬比下 mac 下執行效果, 放大或者縮小視窗大小,這個三角形會按照比率做自動變化。
源碼分析如下:
三角形座標定義:
Vertex (頂點)
頂點是3D建模時用到的最小構成元素,頂點定義為兩條或是多條邊交會的地方。在3D模型中一個頂點可以為多條邊,面或是多邊形所共用。一個頂點也可以代表一個點光源或是Camera的位置。
定義了三角形的三個頂點和對應的代碼實現:
這裡把float的座標轉換成 byte數組,它將會傳入OpenGl ES的圖形處理流程中。
這裡定義的不是固定像素尺寸,而是相對螢幕大小的尺寸(螢幕大小是2*2,具體原因後面解釋)。
使用 OpenGL 畫圖形需要定義的項
使用OpenGL ES 2.0畫一個定義好的形狀需要較多代碼,因為你需要提供很多圖形渲染流程的細節。具體而言,你必須定義如下幾項:
- 頂點著色器(Vertex Shader):用來渲染形狀頂點的OpenGL ES代碼。
- 片段著色器(Fragment Shader):使用顏色或紋理渲染形狀表面的OpenGL ES代碼。
- 程式(Program):一個OpenGL ES對象,包含了你希望用來繪製一個或更多圖形所要用到的著色器。
你需要至少一個頂點著色器來繪製一個形狀,以及一個片段著色器為該形狀上色。這些著色器必須被編譯然後添加到一個OpenGL ES Program當中,並利用它來繪製形狀。
程式(Program)
為了繪製你的圖形,你必須編譯著色器代碼,將它們添加至一個OpenGL ES Program對象中,然後執行連結。在你的繪製對象時,上述步驟就只執行一次。
這裡的頂點著色器和片段著色器的具體含義後面分析。
著色器包含了OpenGL Shading Language(GLSL)代碼,它必須先被編譯然後才能在OpenGL環境中使用。要編譯這些代碼,需要在你的渲染器類中建立一個輔助方法,我們可以看到 gomobile 幫我們把這個方法封裝在CreateProgram 中了 :
下面代碼是 golang.org/x/mobile/exp/gl/glutil 的代碼。
Note:編譯OpenGL ES著色器及連結操作對於CPU周期和處理時間而言,消耗是巨大的,所以你應該避免重複執行這些事情。你應該在構建你的應用時,確保它們只被建立了一次,並且緩衝以備後續使用。
傳值給OpenGL
儘管我們已經有了資料,OpenGL並不能直接使用它們。OpenGL對它能讀取的記憶體有些限制。你可以按需分配你的頂點資料,但是這些記憶體對OpenGL並不直接可見。因此,第一步就是分配OpenGL可見的記憶體,並填充我們的資料。這是通過緩衝對象(buffer object,以下簡稱BO)來實現的。
一個緩衝對象,是一個線性數組形式的記憶體,由OpenGL根據使用者要求管理和分配。這塊記憶體的內容可由使用者控制,但是使用者也僅能間接地控制。可以把buffer object當做GPU記憶體中的數組。
GPU可以快速讀取它,因此在它裡面儲存資料有效能優勢。
前面我們已經有了頂點資料,問題是它在我們的RAM中而不是OpenGL的記憶體中。要把他搬到OpenGL的記憶體,需要做上面三行代碼:
第一行,建立buffer object。這時候我們還未給他分配任何空間。
第二行,BindBuffer函數將建立的BO綁定到ARRAY_BUFFER上下文中。
第三行,我們通過BufferData函數完成OpenGL中分配空間+資料拷貝的工作。
當這個函數執行完後,BO中就有了頂點資料了。這個函數最後的參數可以是下面值:
- STATIC_DRAW 儲存的資料內容只被程式定義一次,GL繪製命令可以使用多次。本文執行個體代碼用的是這個。
- DYNAMIC_DRAW 儲存的資料內容將被程式重複定義,GL繪製命令可以使用多次。
後面我們看到 triangleData 再也沒有被傳遞給 OpenGL 過,就是因為這個參數的讓後面複用了。
管道開關
有了頂點的定義,下面一步就是如何將它們傳給OpenGL ES庫,OpenGL ES提供一個成為”管道Pipeline”的機制,這個管道定義了一些“開關”來控制OpenGL ES支援的某些功能,預設情況這些功能是關閉的,如果需要使用OpenGL ES的這些功能,需要明確告知OpenGL “管道”開啟所需功能。
對於這個樣本,需要告訴OpenGL庫開啟 Vertex buffer以便使用頂點座標Buffer。
要注意的使用完某個功能之後,要關閉這個功能以免影響後續操作:
頂點著色器
這個例子中的 vertexShader 就是 頂點著色器
這裡的 version 100 是 Android、iOS、WebGL 使用的 OpenGL ES 2.0 對應的GLSL ES版本。參考: http://blog.csdn.net/u013467442/article/details/46765335
uniform vec2 offset;
attribute vec4 position;
是兩個輸入參數,uniform 標示唯讀、attribute 標示專用於頂點著色器,唯讀。
vec2 標示只包含2個浮點的向量,vec4標示包含4個浮點的向量。
gl_Position是頂點著色器裁切空間輸出的位置向量。如果你想讓螢幕上渲染出東西gl_Position必須使用。否則我們什麼都看不到。
注意 gl_Position 的座標系跟手機螢幕座標像素的座標系不一樣。它的座標系是 右手座標系統。 詳見後面。
頂點著色器 position 賦值
GLSL 中的position (attribute 類型)是模型的原始座標,即這裡的 triangleData,
go中 position 變數則是這個位置指標。
對應代碼如下:
glctx.GetAttribLocation 返回指定屬性變數的位置。
returns the location of an attribute variable.
在 GetAttribLocation 這個函數這裡完成了映射捆綁。
賦值代碼在下面:
有關這個函數的聲明如下:
直接給 VertexAttribPointer 賦值是不支援的, 你需要使用 BindBuffer 綁定資料緩衝區,然後用 BufferData 來填充數值。本例子在初始化時賦值用了標示 STATIC_DRAW , 即後面可以複用這個賦值。
Direct use of VertexAttribPointer to load data into OpenGL is not supported via the Go bindings. Instead, use BindBuffer with an ARRAY_BUFFER and then fill it using BufferData.
The stride argument specifies the byte offset between consecutive vertex attributes.
offset 的賦值
GLSL 中 offset 只是 uniform 的類型, 這樣的賦值就簡單多了。
建立 go 跟 openGL 的關聯指標
賦值,直接賦值,沒有上面 position 那麼多彎彎繞。
頂點的位置計算
計算中用到的幾個值:
1、touchX、touchY 觸點位置。
預設 touchX、touchY 是在螢幕的正中央,當有 touch 事件發生時,則是螢幕的對應位置。 以螢幕的實際像素為準。
螢幕的座標系如,左上方為原點,向右是X軸,向下是Y軸。
2、sz.WidthPx、sz.HeightPx 螢幕的實際大小尺寸。
對應座標也如。 WidthPx 是寬度、 HeightPx 是長度。
3、傳遞入 OpenGL 的 offset 值
offset.x 相對螢幕寬度的觸點位置 touchX/float32(sz.WidthPx)
offset.y 項目螢幕高度的觸點位置 touchY/float32(sz.HeightPx)
它們的值都是 0-1 之間。
4、傳遞入 OpenGL 的 position 值。
這個值就是 triangleData 的值,只是傳遞方法有點繞。
5、OpenGL 實際運算的 offset4
這裡要做座標體系的轉換。
vec4 offset4 = vec4(2.0*offset.x-1.0, 1.0-2.0*offset.y, 0, 0);
x 放大2倍,
我們會把下面這個基於像素的座標系(長寬 0到1)
轉換成下面OpenGL的座標系(OpenGL中使用, 長寬 -1到1)。
轉換的演算法就是上面的,2倍減一, 由於Y軸涉及到翻轉,再外面加一個負數。
6、OpenGL 的實際位置
在offset4位置畫模型,就是模型的具體實際位置。
gl_Position = position + offset4;
注意,這裡我們畫的三角形 top left 點 是上面的,而不是向下, 模型我們直接就用的 OpenGL的這個座標系。
由於座標系是從 -1到 1, 長度 0.4 就是 1/5 , 我們可以看到畫出來的三角形, 長寬 分別是螢幕尺寸的 1/5.
片段著色器
映像顏色的設定
fragmentShader
就是片段著色器
precision用來確定預設精度修飾符,precision mediump float; 基本相當於中等精度。
參考: http://blog.csdn.net/wangyuchun_799/article/details/7752322
uniform vec4 color 唯讀,4個浮點的向量 color。
所畫圖的顏色賦值過程
綁定關係
在應用啟動時,完成綁定關係
賦值
每次需要繪畫時,賦值。
Render (渲染)
我們已定義好了多邊形,下面就要瞭解如和使用OpenGL ES的API來繪製(渲染)這個多邊形了。OpenGL ES提供了兩類方法來繪製一個空間幾何圖形:
DrawArrays 使用VetexBuffer 來繪製,頂點的順序由vertexBuffer中的順序指定。
DrawElements 可以重新定義頂點的順序,頂點的順序由indices Buffer 指定。
前面我們已定義裡頂點數組,因此我們將採用 DrawArrays 來繪製多邊形。
同樣的頂點,可以定義的幾何圖形可以有所不同,比如三個頂點,可以代表三個獨立的點,也可以表示一個三角形,這就需要使用mode參數 來指明所需繪製的幾何圖形的基本類型。
有關各個 mode 的幾何類型請參考: http://www.imobilebbs.com/wordpress/archives/1512
Coordinate System座標系
OpenGL使用了右手座標系統,右手座標系判斷方法:在空間直角座標系中,讓右手拇指指向x軸的正方向,食指指向y軸的正方向,如果中指能指向z軸的正方向,則稱這個座標係為右手直角座標系。
座標轉換
OpenGL 繪圖過程中涉及到 座標系的轉換, 類似。
圖來自: http://zhangwenli.com/blog/2015/08/28/opengl-matrix-transformations/
使用e.External 避免多次刷屏繪畫
if glctx == nil || e.External {
// As we are actively painting as fast as
// we can (usually 60 FPS), skip any paint
// events sent by the system.
continue
}
顯示 fps 調試資訊
參考資料:
package gl 文檔
https://godoc.org/golang.org/x/mobile/gl
Android OpenGL ES 開發教程 從入門到精通 這裡講的是 OpenGL ES 1.1的知識
http://blog.csdn.net/mapdigit/article/details/7526556
繪製形狀
http://hukai.me/android-training-course-in-chinese/graphics/opengl/draw.html
OpenGL ES2 學習教程4——Shader語言
https://segmentfault.com/a/1190000004410579