前面介紹了使用 Android 編寫 OpenGL ES 應(yīng)用的程序框架,本篇介紹3D繪圖的一些基本構(gòu)成要素,最終將實(shí)現(xiàn)一個(gè)多邊形的繪制。
一個(gè) 3D 圖形通常是由一些小的基本元素(頂點(diǎn),邊,面,多邊形)構(gòu)成,每個(gè)基本元素都可以單獨(dú)來操作。
頂點(diǎn)是 3D 建模時(shí)用到的最小構(gòu)成元素,頂點(diǎn)定義為兩條或是多條邊交會(huì)的地方。在 3D 模型中一個(gè)頂點(diǎn)可以為多條邊,面或是多邊形所共享。一個(gè)頂點(diǎn)也可以代表一個(gè)點(diǎn)光源或是 Camera 的位置。下圖中標(biāo)識(shí)為黃色的點(diǎn)為一個(gè)頂點(diǎn)(Vertex)。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/1.png" alt="" />
在 Android 系統(tǒng)中可以使用一個(gè)浮點(diǎn)數(shù)數(shù)組來定義一個(gè)頂點(diǎn),浮點(diǎn)數(shù)數(shù)組通常放在一個(gè) Buffer(java.nio)中來提高性能。
比如:下圖中定義了四個(gè)頂點(diǎn)和對應(yīng)的 Android 頂點(diǎn)定義:
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/2.png" alt="" />
private float vertices[] = {
-1.0f, 1.0f, 0.0f, // 0, Top Left
-1.0f, -1.0f, 0.0f, // 1, Bottom Left
1.0f, -1.0f, 0.0f, // 2, Bottom Right
1.0f, 1.0f, 0.0f, // 3, Top Right
};
為了提高性能,通常將這些數(shù)組存放到 java.io 中定義的 Buffer 類中:
// a float is 4 bytes, therefore we multiply the
//number if vertices with 4.
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
有了頂點(diǎn)的定義,下面一步就是如何將它們傳給 OpenGL ES 庫,OpenGL ES 提供一個(gè)成為”管道 Pipeline ”的機(jī)制,這個(gè)管道定義了一些“開關(guān)”來控制 OpenGL ES 支持的某些功能,缺省情況這些功能是關(guān)閉的,如果需要使用 OpenGL ES 的這些功能,需要明確告知 OpenGL “管道”打開所需功能。因此對于我們的這個(gè)示例,需要告訴 OpenGL 庫打開 Vertex buffer 以便傳入頂點(diǎn)坐標(biāo) Buffer。要注意的使用完某個(gè)功能之后,要關(guān)閉這個(gè)功能以免影響后續(xù)操作:
// Enabled the vertex buffer for writing and to be used during rendering.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);// OpenGL docs.
// Specifies the location and data format of an array of vertex
// coordinates to use when rendering.
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer); // OpenGL docs.
When you are done with the buffer don't forget to disable it.
// Disable the vertices buffer.
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);// OpenGL docs.
邊定義為兩個(gè)頂點(diǎn)之間的線段。邊是面和多邊形的邊界線。在 3D 模型中,邊可以被相鄰的兩個(gè)面或是多邊形形共享。對一個(gè)邊做變換將影響邊相接的所有頂點(diǎn),面或多邊形。在 OpenGL 中,通常無需直接來定義一個(gè)邊,而是通過頂點(diǎn)定義一個(gè)面,從而由面定義了其所對應(yīng)的三條邊??梢酝ㄟ^修改邊的兩個(gè)頂點(diǎn)來更改一條邊,下圖黃色的線段代表一條邊:
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/3.png" alt="" />
在 OpenGL ES 中,面特指一個(gè)三角形,由三個(gè)頂點(diǎn)和三條邊構(gòu)成,對一個(gè)面所做的變化影響到連接面的所有頂點(diǎn)和邊,面多邊形。下圖黃色區(qū)域代表一個(gè)面。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/4.png" alt="" />
定義面的頂點(diǎn)的順序很重要 在拼接曲面的時(shí)候,用來定義面的頂點(diǎn)的順序非常重要,因?yàn)轫旤c(diǎn)的順序定義了面的朝向(前向或是后向),為了獲取繪制的高性能,一般情況不會(huì)繪制面的前面和后面,只繪制面的“前面”。雖然“前面”“后面”的定義可以應(yīng)人而易,但一般為所有的“前面”定義統(tǒng)一的頂點(diǎn)順序(順時(shí)針或是逆時(shí)針方向)。
下面代碼設(shè)置逆時(shí)針方法為面的“前面”:
gl.glFrontFace(GL10.GL_CCW);
打開 忽略“后面”設(shè)置:
gl.glEnable(GL10.GL_CULL_FACE);
明確指明“忽略“哪個(gè)面的代碼如下:
gl.glCullFace(GL10.GL_BACK);
多邊形由多個(gè)面(三角形)拼接而成,在三維空間上,多邊形并一定表示這個(gè) Polygon 在同一平面上。這里我們使用缺省的逆時(shí)針方向代表面的“前面 Front),下圖黃色區(qū)域?yàn)橐粋€(gè)多邊形。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/5.png" alt="" />
來看一個(gè)多邊形的示例在 Android 系統(tǒng)如何使用頂點(diǎn)和 buffer 來定義,如下圖定義了一個(gè)正方形:
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/6.png" alt="" />
對應(yīng)的頂點(diǎn)和buffer 定義代碼:
private short[] indices = { 0, 1, 2, 0, 2, 3 };
To gain some performance we also put this ones in a byte buffer.
// short is 2 bytes, therefore we multiply the number if vertices with 2.
ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
ibb.order(ByteOrder.nativeOrder());
ShortBuffer indexBuffer = ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);
我們已定義好了多邊形,下面就要了解如和使用 OpenGL ES 的 API 來繪制(渲染)這個(gè)多邊形了。OpenGL ES 提供了兩類方法來繪制一個(gè)空間幾何圖形:
前面我們已定義里頂點(diǎn)數(shù)組,因此我們將采用 glDrawElements 來繪制多邊形。
同樣的頂點(diǎn),可以定義的幾何圖形可以有所不同,比如三個(gè)頂點(diǎn),可以代表三個(gè)獨(dú)立的點(diǎn),也可以表示一個(gè)三角形,這就需要使用 mode 來指明所需繪制的幾何圖形的基本類型。
繪制獨(dú)立的點(diǎn)。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/7.png" alt="" />
繪制一系列線段。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/8.png" alt="" />
類同上,但是首尾相連,構(gòu)成一個(gè)封閉曲線。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/9.png" alt="" />
頂點(diǎn)兩兩連接,為多條線段構(gòu)成。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/10.png" alt="" />
每隔三個(gè)頂點(diǎn)構(gòu)成一個(gè)三角形,為多個(gè)三角形組成。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/11.png" alt="" />
每相鄰三個(gè)頂點(diǎn)組成一個(gè)三角形,為一系列相接三角形構(gòu)成。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/12.png" alt="" />
以一個(gè)點(diǎn)為三角形公共頂點(diǎn),組成一系列相鄰的三角形。
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/13.png" alt="" />
下面可以來繪制正方形了,在項(xiàng)目中添加一個(gè) Square.java 定義如下:
package se.jayway.opengl.tutorial;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.opengles.GL10;
public class Square {
// Our vertices.
private float vertices[] = {
-1.0f, 1.0f, 0.0f, // 0, Top Left
-1.0f, -1.0f, 0.0f, // 1, Bottom Left
1.0f, -1.0f, 0.0f, // 2, Bottom Right
1.0f, 1.0f, 0.0f, // 3, Top Right
};
// The order we like to connect them.
private short[] indices = { 0, 1, 2, 0, 2, 3 };
// Our vertex buffer.
private FloatBuffer vertexBuffer;
// Our index buffer.
private ShortBuffer indexBuffer;
public Square() {
// a float is 4 bytes, therefore we
// multiply the number if
// vertices with 4.
ByteBuffer vbb
= ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
// short is 2 bytes, therefore we multiply
//the number if
// vertices with 2.
ByteBuffer ibb
= ByteBuffer.allocateDirect(indices.length * 2);
ibb.order(ByteOrder.nativeOrder());
indexBuffer = ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);
}
/**
* This function draws our square on screen.
* @param gl
*/
public void draw(GL10 gl) {
// Counter-clockwise winding.
gl.glFrontFace(GL10.GL_CCW);
// Enable face culling.
gl.glEnable(GL10.GL_CULL_FACE);
// What faces to remove with the face culling.
gl.glCullFace(GL10.GL_BACK);
// Enabled the vertices buffer for writing
//and to be used during
// rendering.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// Specifies the location and data format of
//an array of vertex
// coordinates to use when rendering.
gl.glVertexPointer(3, GL10.GL_FLOAT, 0,
vertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, indices.length
GL10.GL_UNSIGNED_SHORT, indexBuffer);
// Disable the vertices buffer.
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
// Disable face culling.
gl.glDisable(GL10.GL_CULL_FACE);
}
}
在 OpenGLRenderer 中添加 Square 成員變量并初始化:
// Initialize our square.
Square square = new Square();
并在 public void onDrawFrame(GL10 gl) 添加
// Draw our square.
square.draw(gl);
來繪制這個(gè)正方形,編譯運(yùn)行,什么也沒顯示,這是為什么呢?這是因?yàn)?OpenGL ES 從當(dāng)前位置開始渲染,缺省坐標(biāo)為(0,0,0),和 View port 的坐標(biāo)一樣,相當(dāng)于把畫面放在眼前,對應(yīng)這種情況 OpenGL 不會(huì)渲染離 view Port 很近的畫面,因此我們需要將畫面向后退一點(diǎn)距離:
// Translates 4 units into the screen.
gl.glTranslatef(0, 0, -4);
在編譯運(yùn)行,這次倒是有顯示了,當(dāng)正方形迅速后移直至看不見,這是因?yàn)槊看握{(diào)用onDrawFrame 時(shí),每次都再向后移動(dòng)4個(gè)單位,需要加上重置 Matrix 的代碼。
// Replace the current matrix with the identity matrix
gl.glLoadIdentity();
最終 onDrawFrame 的代碼如下:
public void onDrawFrame(GL10 gl) {
// Clears the screen and depth buffer.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
gl.glTranslatef(0, 0, -4);
// Draw our square.
square.draw(gl); // ( NEW )
}
http://wiki.jikexueyuan.com/project/opengl-es-basics/images/14.png" alt="" />