本篇文章屬于 使用 OpenGL ES 進(jìn)行圖形繪制 這個(gè)系列的第二篇文章麸祷,主要內(nèi)容是介紹在如何在 Android 應(yīng)用中利用定義 OpenGL 中圖形的形狀澎怒。文章中所有的代碼示例都已放在 Github 上,可以去項(xiàng)目 OpenGL-ES-Learning 中查看 阶牍。
如同學(xué)習(xí)繪制自定義 View 一樣喷面,定義圖形的形狀是實(shí)現(xiàn)各種復(fù)雜的圖形的基礎(chǔ),下面將介紹 OpenGL ES 相對(duì)于 Android 設(shè)備屏幕的坐標(biāo)系走孽、定義形狀和形狀繪制等基礎(chǔ)知識(shí)惧辈。
定義一個(gè)三角形
OpenGL ES 允許我們使用三維空間的坐標(biāo)來(lái)定義繪畫(huà)對(duì)象。所以在我們能畫(huà)三角形之前磕瓷,必須先定義它的坐標(biāo)盒齿。在 OpenGL 中,典型的辦法是為坐標(biāo)定義一個(gè) Float 類(lèi)型的頂點(diǎn)數(shù)組困食。為了效率最大化边翁,我們可以將坐標(biāo)寫(xiě)入一個(gè) ByteBuffer,它將會(huì)傳入 OpenGl ES 的 pipeline 來(lái)處理硕盹。
ByteBuffer 俗稱(chēng)緩沖器符匾,在 NIO 中,數(shù)據(jù)的讀寫(xiě)操作始終是與緩沖區(qū)相關(guān)聯(lián)的瘩例。讀取時(shí)信道 (SocketChannel) 將數(shù)據(jù)讀入緩沖區(qū)啊胶,寫(xiě)入時(shí)首先要將發(fā)送的數(shù)據(jù)按順序填入緩沖區(qū)芒澜。緩沖區(qū)是定長(zhǎng)的,基本上它只是一個(gè)列表创淡,它的所有元素都是基本數(shù)據(jù)類(lèi)型痴晦。ByteBuffer 是最常用的緩沖區(qū),它提供了讀寫(xiě)其他數(shù)據(jù)類(lèi)型的方法琳彩,且信道的讀寫(xiě)方法只接收 ByteBuffer誊酌。關(guān)于 ByteBuffer 的更多內(nèi)容,推薦一篇文章 Android中直播視頻技術(shù)探究之—基礎(chǔ)核心類(lèi)ByteBuffer解析
public class Triangle {
/**
* 定義三角形頂點(diǎn)的坐標(biāo)數(shù)據(jù)的浮點(diǎn)型緩沖區(qū)
*/
private FloatBuffer vertexBuffer;
// 坐標(biāo)數(shù)組中的頂點(diǎn)坐標(biāo)個(gè)數(shù)
static final int COORDINATES_PRE_VERTEX = 3;
static float triangleCoords[] = { // 以逆時(shí)針順序;
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
};
// Set color with red, green, blue and alpha (opacity) values
float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
public Triangle(){
// 初始化形狀中頂點(diǎn)坐標(biāo)數(shù)據(jù)的字節(jié)緩沖區(qū)
// 通過(guò) allocateDirect 方法獲取到 DirectByteBuffer 實(shí)例
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(
// 頂點(diǎn)坐標(biāo)個(gè)數(shù) * 坐標(biāo)數(shù)據(jù)類(lèi)型 float 一個(gè)是 4 bytes
triangleCoords.length * 4
);
// 設(shè)置緩沖區(qū)使用設(shè)備硬件的原本字節(jié)順序進(jìn)行讀取;
byteBuffer.order(ByteOrder.nativeOrder());
// 因?yàn)?ByteBuffer 是將數(shù)據(jù)移進(jìn)移出通道的唯一方式使用露乏,這里使用 “as” 方法從 ByteBuffer 中獲得一個(gè)基本類(lèi)型緩沖區(qū)(浮點(diǎn)緩沖區(qū))
vertexBuffer = byteBuffer.asFloatBuffer();
// 把頂點(diǎn)坐標(biāo)信息數(shù)組存儲(chǔ)到 FloatBuffer
vertexBuffer.put(triangleCoords);
// 設(shè)置從緩沖區(qū)的第一個(gè)位置開(kāi)始讀取頂點(diǎn)坐標(biāo)信息
vertexBuffer.position(0);
}
}
跟 View 的坐標(biāo)系原點(diǎn)位于屏幕左上角不同碧浊,默認(rèn)情況下 OpenGL ES 會(huì)假定一個(gè)坐標(biāo)系,在這個(gè)坐標(biāo)系中瘟仿,[0, 0, 0](分別對(duì)應(yīng)X軸坐標(biāo), Y軸坐標(biāo), Z軸坐標(biāo))對(duì)應(yīng)的是GLSurfaceView 的中心箱锐。如 [1, 1, 0] 對(duì)應(yīng)的是右上角,[-1, -1, 0] 對(duì)應(yīng)的則是左下角劳较。
在 OpenGL 里驹止,我們要渲染的一切物體都要映射到 X 軸和 Y 軸上 [-1,1] 的范圍內(nèi)观蜗,對(duì)于Z軸也一樣臊恋。這個(gè)范圍內(nèi)的坐標(biāo)被稱(chēng)為歸一化設(shè)備坐標(biāo),其獨(dú)立于屏幕實(shí)際尺寸或形狀墓捻。也就是說(shuō)在 Android 設(shè)備上顯示圖形時(shí)屏幕的尺寸和形狀雖然會(huì)有所不同抖仅,但是 OpenGL 假設(shè)了一個(gè)平方均勻的坐標(biāo)系,默認(rèn)情況下將這些坐標(biāo)按比例繪制非正方形屏幕上砖第,就好像它是完全正方形一樣撤卢。
如上圖所示的坐標(biāo)系,左圖是默認(rèn)的 OpenGL 坐標(biāo)系梧兼,右圖是實(shí)際展示 Android 設(shè)備屏幕時(shí)的坐標(biāo)系放吩,會(huì)看到三角形會(huì)有一個(gè)明顯的拉伸。默認(rèn)情況下袱院,對(duì)于 OpenGL 而言不管硬件設(shè)備屏幕是不是正方形屎慢,都把它當(dāng)作一個(gè)正方形來(lái)處理瞭稼,三維坐標(biāo)都限定在 [-1, 1]內(nèi)忽洛。 所以 Open GL 的坐標(biāo)體系獨(dú)立于實(shí)際的屏幕尺寸。
要處理畫(huà)面被拉伸的問(wèn)題环肘,可以考慮調(diào)整坐標(biāo)空間欲虚,把屏幕的形狀考慮在內(nèi),可行的一個(gè)方法是把較小的范圍固定在 [-1,1] 內(nèi)悔雹,而按屏幕尺寸的比例調(diào)整較大的范圍复哆。這里推薦一篇文章:Android OpenGL ES 調(diào)整屏幕的寬高比欣喧,文章中提到了如何處理 Open GL 在實(shí)際展示時(shí)寬高比控制。而這篇文章:OpenGL ES 透視投影則是對(duì)歸一化設(shè)備坐標(biāo)到視口(視口:OpenGL 渲染操作最終顯示窗口)的窗口坐標(biāo)轉(zhuǎn)化說(shuō)明梯找。這些內(nèi)容建議暫時(shí)放一下唆阿,遇到相關(guān)問(wèn)題時(shí)在深入了解。
注意到上面這個(gè)形狀的坐標(biāo)是以逆時(shí)針順序定義的锈锤。繪制的順序非常關(guān)鍵驯鳖,因?yàn)樗x了哪一面是形狀的正面(希望繪制的一面),以及背面(使用 OpenGL ES 的 Cull Face 功能可以讓背面不要繪制)久免。更多關(guān)于該方面的信息浅辙,可以閱讀 OpenGL ES 開(kāi)發(fā)手冊(cè)。
定義一個(gè)矩形
在 OpenGL 中定義三角形非常簡(jiǎn)單阎姥,那定義一個(gè)矩形呢记舆?有很多方法可以用來(lái)定義矩形,不過(guò)在 OpenGL ES 中最典型的辦法是使用兩個(gè)三角形拼接在一起:
同樣的我們通過(guò)按逆時(shí)針順序?yàn)槿切雾旤c(diǎn)定義坐標(biāo)來(lái)表示這個(gè)圖形呼巴,并將值放入一個(gè) ByteBuffer 中泽腮。為了避免由兩個(gè)三角形重合的那條邊的頂點(diǎn)被重復(fù)定義,可以使用一個(gè)繪制列表(drawing list)來(lái)告訴 OpenGL ES 繪制順序衣赶。下面是代碼樣例:
public class Square {
/**
* 頂點(diǎn)坐標(biāo)數(shù)據(jù)緩沖區(qū)(float 類(lèi)型)
*/
private FloatBuffer vertexBuffer;
/**
* 繪制順序數(shù)據(jù)緩沖區(qū)(short類(lèi)型)
*/
private ShortBuffer drawListBuffer;
/**
* 頂點(diǎn)坐標(biāo)數(shù)據(jù)的數(shù)組
*/
static float squareCoords[] = {
-0.5f, 0.5f, 0.0f, // top left
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, // bottom right
0.5f, 0.5f, 0.0f }; // top right
/**
* 繪制頂點(diǎn)順序
*/
private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // order to draw vertices
public Square() {
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(
// (# of coordinate values * 4 bytes per float)
squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords);
vertexBuffer.position(0);
// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(
// (# of coordinate values * 2 bytes per short)
drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
}
}
該樣例可以看作是一個(gè)如何使用 OpenGL 創(chuàng)建復(fù)雜圖形的啟發(fā)盛正,通常來(lái)說(shuō)我們需要使用三角形的集合來(lái)繪制對(duì)象。
文章中所有的代碼示例都已放在 Github 上屑埋,可以去項(xiàng)目 OpenGL-ES-Learning 中查看 豪筝。
上述內(nèi)容主要是對(duì)如何定義簡(jiǎn)單形狀進(jìn)行一個(gè)說(shuō)明,了解 OpenGL 坐標(biāo)體系規(guī)則摘能,下面將了解如何在屏幕上畫(huà)這些形狀续崖。