opengl es 基礎知識瞭解
前面介紹了使用Android 編寫OpenGL ES應用的程式框架,本篇介紹3D繪圖的一些基本構成要素,最終將實現一個多邊形的繪製。
一個3D圖形通常是由一些小的基本元素(頂點,邊,面,多邊形)構成,每個基本元素都可以單獨來操作。
Vertex (頂點)
頂點是3D建模時用到的最小構成元素,頂點定義為兩條或是多條邊交會的地方。在3D模型中一個頂點可以為多條邊,面或是多邊形所共享。一個頂點也可以代表一個點光源或是Camera的位置。下圖中標識為黃色的點為一個頂點(Vertex)。
在Android系統中可以使用一個浮點數陣列來定義一個頂點,浮點數陣列通常放在一個Buffer(java.nio)中來提高效能。
比如:下圖中定義了四個頂點和對應的Android 頂點定義:
1 2 3 4 5 6 |
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
};
|
為了提高效能,通常將這些陣列存放到java.io 中定義的Buffer類中:
1 2 3 4 5 6 7 |
// 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 );
|
有了頂點的定義,下面一步就是如何將它們傳給OpenGL ES庫,OpenGL ES提供一個成為”管道Pipeline”的機制,這個管道定義了一些“開關”來控制OpenGL ES支援的某些功能,預設情況這些功能是關閉的,如果需要使用OpenGL ES的這些功能,需要明確告知OpenGL “管道”開啟所需功能。因此對於我們的這個示例,需要告訴OpenGL庫開啟 Vertex buffer以便傳入頂點座標Buffer。要注意的使用完某個功能之後,要關閉這個功能以免影響後續操作:
1 2 3 4 5 6 7 8 |
// 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.
|
Edge(邊)
邊定義為兩個頂點之間的線段。邊是面和多邊形的邊界線。在3D模型中,邊可以被相鄰的兩個面或是多邊形形共享。對一個邊做變換將影響邊相接的所有頂點,面或多邊形。在OpenGL中,通常無需直接來定義一個邊,而是通過頂點定義一個面,從而由面定義了其所對應的三條邊。可以通過修改邊的兩個頂點來更改一條邊,下圖黃色的線段代表一條邊:
Face (面)
在OpenGL ES中,面特指一個三角形,由三個頂點和三條邊構成,對一個面所做的變化影響到連接面的所有頂點和邊,面多邊形。下圖黃色區域代表一個面。
定義面的頂點的順序很重要 在拼接曲面的時候,用來定義面的頂點的順序非常重要,因為頂點的順序定義了面的朝向(前向或是後向),為了獲取繪製的高效能,一般情況不會繪製面的前面和後面,只繪製面的“前面”。雖然“前面”“後面”的定義可以應人而易,但一般為所有的“前面”定義統一的頂點順序(順時針或是逆時針方向)。
下面程式碼設定逆時針方法為面的“前面”:
1 |
gl.glFrontFace(GL10.GL_CCW);
|
開啟 忽略“後面”設定:
1 |
gl.glEnable(GL10.GL_CULL_FACE);
|
明確指明“忽略“哪個面的程式碼如下:
1 |
gl.glCullFace(GL10.GL_BACK);
|
Polygon (多邊形)
多邊形由多個面(三角形)拼接而成,在三維空間上,多邊形並一定表示這個Polygon在同一平面上。這裡我們使用預設的逆時針方向代表面的“前面Front),下圖黃色區域為一個多邊形。
來看一個多邊形的示例在Android系統如何使用頂點和buffer 來定義,如下圖定義了一個正方形:
1 2 3 4 5 6 7 8 |
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 );
|
Render (渲染)
我們已定義好了多邊形,下面就要了解如和使用OpenGL ES的API來繪製(渲染)這個多邊形了。OpenGL ES提供了兩類方法來繪製一個空間幾何圖形:
- public abstract void glDrawArrays(int mode, int first, int count) 使用VetexBuffer 來繪製,頂點的順序由vertexBuffer中的順序指定。
- public abstract void glDrawElements(int mode, int count, int type, Buffer indices) ,可以重新定義頂點的順序,頂點的順序由indices Buffer 指定。
前面我們已定義裡頂點陣列,因此我們將採用glDrawElements 來繪製多邊形。
同樣的頂點,可以定義的幾何圖形可以有所不同,比如三個頂點,可以代表三個獨立的點,也可以表示一個三角形,這就需要使用mode 來指明所需繪製的幾何圖形的基本型別。
GL_POINTS
繪製獨立的點。
GL_LINE_STRIP
繪製一系列線段。
GL_LINE_LOOP
類同上,但是首尾相連,構成一個封閉曲線。
GL_LINES
頂點兩兩連線,為多條線段構成。
GL_TRIANGLES
每隔三個頂點構成一個三角形,為多個三角形組成。
每相鄰三個頂點組成一個三角形,為一系列相接三角形構成。
GL_TRIANGLE_FAN
以一個點為三角形公共頂點,組成一系列相鄰的三角形。
下面可以來繪製正方形了,在專案中新增一個Square.java 定義如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
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成員變數並初始化:
1 2 |
// Initialize our square.
Square square =
new Square();
|
並在public void onDrawFrame(GL10 gl) 新增
1 2 |
// Draw our square.
square.draw(gl);
|
來繪製這個正方形,編譯執行,什麼也沒顯示,這是為什麼呢?這是因為OpenGL ES從當前位置開始渲染,預設座標為(0,0,0),和View port 的座標一樣,相當於把畫面放在眼前,對應這種情況OpenGL不會渲染離view Port很近的畫面,因此我們需要將畫面向後退一點距離:
1 2 |
// Translates 4 units into the screen.
gl.glTranslatef( 0 ,
0 , - 4 );
|
在編譯執行,這次倒是有顯示了,當正方形迅速後移直至看不見,這是因為每次呼叫onDrawFrame 時,每次都再向後移動4個單位,需要加上重置Matrix的程式碼。
1 2 |
// Replace the current matrix with the identity matrix
gl.glLoadIdentity();
|
最終onDrawFrame的程式碼如下:
1 2 3 4 5 6 7 8 9 10 |
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 )
}
|
本篇程式碼下載