Android OpenGL ES 簡明開發教程三 3D繪圖基本概念
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow
也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!
前面介紹了使用Android 編寫OpenGL ES應用的程式框架,本篇介紹3D繪圖的一些基本構成要素,最終將實現一個多邊形的繪製。
一個3D圖形通常是由一些小的基本元素(頂點,邊,面,多邊形)構成,每個基本元素都可以單獨來操作。
Vertex (頂點)
頂點是3D建模時用到的最小構成元素,頂點定義為兩條或是多條邊交會的地方。在3D模型中一個頂點可以為多條邊,面或是多邊形所共享。一個頂點也可以代表一個點光源或是Camera的位置。下圖中標識為黃色的點為一個頂點(Vertex)。
在Android系統中可以使用一個浮點數陣列來定義一個頂點,浮點數陣列通常放在一個Buffer(java.nio)中來提高效能。
比如:下圖中定義了四個頂點和對應的Android 頂點定義:
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類中:
// 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);
Edge(邊)
邊定義為兩個頂點之間的線段。邊是面和多邊形的邊界線。在3D模型中,邊可以被相鄰的兩個面或是多邊形形共享。對一個邊做變換將影響邊相接的所有頂點,面或多邊形。在OpenGL中,通常無需直接來定義一個邊,而是通過頂點定義一個面,從而由面定義了其所對應的三條邊。可以通過修改邊的兩個頂點來更改一條邊,下圖黃色的線段代表一條邊:
Face (面)
在OpenGL ES中,面特指一個三角形,由三個頂點和三條邊構成,對一個面所做的變化影響到連接面的所有頂點和邊,面多邊形。下圖黃色區域代表一個面。
定義面的頂點的順序很重要 在拼接曲面的時候,用來定義面的頂點的順序非常重要,因為頂點的順序定義了面的朝向(前向或是後向),為了獲取繪製的高效能,一般情況不會繪製面的前面和後面,只繪製面的“前面”。雖然“前面”“後面”的定義可以應人而易,但一般為所有的“前面”定義統一的頂點順序(順時針或是逆時針方向)。
下面程式碼設定逆時針方法為面的“前面”:
gl.glFrontFace(GL10.GL_CCW);
開啟 忽略“後面”設定:
gl.glEnable(GL10.GL_CULL_FACE);
明確指明“忽略“哪個面的程式碼如下:
gl.glCullFace(GL10.GL_BACK);
Polygon (多邊形)
多邊形由多個面(三角形)拼接而成,在三維空間上,多邊形並一定表示這個Polygon在同一平面上。這裡我們使用預設的逆時針方向代表面的“前面Front),下圖黃色區域為一個多邊形。
來看一個多邊形的示例在Android系統如何使用頂點和buffer 來定義,如下圖定義了一個正方形:
對應的頂點和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);
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 定義如下:
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);
來繪製這個正方形,編譯執行,什麼也沒顯示,這是為什麼呢?這是因為OpenGL ES從當前位置開始渲染,預設座標為(0,0,0),和View port 的座標一樣,相當於把畫面放在眼前,對應這種情況OpenGL不會渲染離view Port很近的畫面,因此我們需要將畫面向後退一點距離:
// Translates 4 units into the screen.
gl.glTranslatef(
0
,
0
, -
4
);
在編譯執行,這次倒是有顯示了,當正方形迅速後移直至看不見,這是因為每次呼叫onDrawFrame 時,每次都再向後移動4個單位,需要加上重置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 ) }
本篇程式碼
下載
// Initialize our square.
Square square =
new
Square();