我有一個預感,看了我前幾篇教程的人,可能會問我:這是一系列3D教程,爲何講的都是2D的事呢?那麼在接下來這篇教程中,咱們來建立一些3D的渲染網格。這也是後面的教程所須要的準備。
java
在當初我開始學習openGL的時候,我也很困惑如何用編程方式取實現立方體,圓錐體等等。我想要它很容易的可以被集成到個人場景中。因此這篇教程將會講解如何建立一些初級的立體模型。這可能不是效率最高的方式,可是確實是可以實現的一種方式。
android
設計一個openGL 框架之初,最好是繪製組合圖。以下是我如何開始的示意圖:編程
讓咱們開始製造這些組合吧。
框架
爲渲染的網格建立一個基礎類是個不錯的主意。就讓咱們從建立一個叫Mesh的類開始。
ide
package se.jayway.opengl.tutorial.mesh; public class Mesh { }
咱們從以前的例子裏拷貝過draw方法,因爲我在教程一中寫過這個方法,因此我這裏只展現一下:
學習
// 頂點緩衝 private FloatBuffer verticesBuffer = null; // 渲染順序緩衝 private ShortBuffer indicesBuffer = null; // 順序緩衝的數量 private int numOfIndices = -1; // 純色 private float[] rgba = new float[]{1.0f, 1.0f, 1.0f, 1.0f}; // 漸變色 private FloatBuffer colorBuffer = null; public void draw(GL10 gl) { // 逆時針 gl.glFrontFace(GL10.GL_CCW); // 開啓裁剪 gl.glEnable(GL10.GL_CULL_FACE); // 背面裁剪 gl.glCullFace(GL10.GL_BACK); // 、開啓渲染中使用的頂點緩衝 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // 指定頂點緩衝的位置和格式 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, verticesBuffer); // 設置純色 gl.glColor4f(rgba[0], rgba[1], rgba[2], rgba[3]); //漸變色 if ( colorBuffer != null ) { // 開啓顏色緩衝 gl.glEnableClientState(GL10.GL_COLOR_ARRAY); // 指定顏色緩衝的位置 gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer); } gl.glDrawElements(GL10.GL_TRIANGLES, numOfIndices, GL10.GL_UNSIGNED_SHORT, indicesBuffer); // 禁用頂點緩衝 gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); // 禁用面裁剪 gl.glDisable(GL10.GL_CULL_FACE); }
咱們須要子類可以設置頂點和渲染順序的方法,這些方法沒有什麼新的東西,和你以前在教程裏看到的幾乎同樣。
this
protected void setVertices(float[] vertices) { // float爲4字節,因此乘以4 ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); verticesBuffer = vbb.asFloatBuffer(); verticesBuffer.put(vertices); verticesBuffer.position(0); } protected void setIndices(short[] indices) { // short爲2字節,因此長度乘以2 ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2); ibb.order(ByteOrder.nativeOrder()); indicesBuffer = ibb.asShortBuffer(); indicesBuffer.put(indices); indicesBuffer.position(0); numOfIndices = indices.length; } protected void setColor(float red, float green, float blue, float alpha) { // 設置純色 rgba[0] = red; rgba[1] = green; rgba[2] = blue; rgba[3] = alpha; } protected void setColors(float[] colors) { ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4); cbb.order(ByteOrder.nativeOrder()); colorBuffer = cbb.asFloatBuffer(); colorBuffer.put(colors); colorBuffer.position(0); }
我須要添加一些東西。當咱們要處理多個網格時,咱們須要可以獨立的移動和旋轉他們,因此咱們增長旋轉和平移的操做變量:
google
// 平移參數 public float x = 0; public float y = 0; public float z = 0; // 旋轉參數 public float rx = 0; public float ry = 0; public float rz = 0;
並在draw方法中在調用gl.glDrawElements以前使用這些參數:編碼
gl.glTranslatef(x, y, z); gl.glRotatef(rx, 1, 0, 0); gl.glRotatef(ry, 0, 1, 0); gl.glRotatef(rz, 0, 0, 1);
讓咱們開始建立一個平面,你也許會認爲是個簡單的任務,實際上也如此。可是爲了讓它更有趣,更有用,咱們要使用一些不一樣的設置來建立它,好比寬度,深度,多少個寬的片元,多少個深的片元(這裏用的詞直接翻譯過來是碎片,意思是組成平面的元素-——譯者注)。
spa
在下文中,我所說的寬是指x軸方向的長度,深度是指Z軸方向,高是指Y軸方向。
片元是指長度上被分爲多少個部分。這對於你建立一個不是一個總體的平面是頗有用的。若是你建立一個xy上的平面,z不全爲0,取值爲-0.1到0.1之間的隨機數,你將會獲得一個你能夠用在遊戲中作 爲地面的平面,固然你的放上漂亮的紋理。
看下圖,不一樣的碎片組合成不一樣的平面,因爲咱們須要三角形,因此咱們把他們拆分爲兩個三角形。
我討厭那些沒有簡單方法初始化的框架和沒有簡單構造方法的類,因此我會在類裏儘可能寫至少一個構造方法。我給plane的構造方法是:
//建立一個平面,寬高各爲1單位,即一個片元. public Plane()
一個簡單的改變大小的方法:
// 讓你可以定義寬高,可是仍然是一個片元 public Plane(float width, float height)
最後是一個帶不一樣參數的構造方法:
// 全部的設置參數 public Plane(float width, float height, int widthSegments, int heightSegments)
若是我定義一個平面在寬高方向上各有4個這樣的寬高爲1單位的片元,那麼看起來應該是這樣:
上圖中,左圖是表示這個平面上的片元,右圖表示咱們實際上建立的平面的樣子。
package se.jayway.opengl.tutorial.mesh; public class Plane extends Mesh { public Plane() { this(1, 1, 1, 1); } public Plane(float width, float height) { this(width, height, 1, 1); } public Plane(float width, float height, int widthSegments, int heightSegments) { float[] vertices = new float[(widthSegments + 1) * (heightSegments + 1) * 3]; short[] indices = new short[(widthSegments + 1) * (heightSegments + 1) * 6]; float xOffset = width / -2; float yOffset = height / -2; float xWidth = width / (widthSegments); float yHeight = height / (heightSegments); int currentVertex = 0; int currentIndex = 0; short w = (short) (widthSegments + 1); for (int y = 0; y < heightSegments + 1; y++) { for (int x = 0; x < widthSegments + 1; x++) { vertices[currentVertex] = xOffset + x * xWidth; vertices[currentVertex + 1] = yOffset + y * yHeight; vertices[currentVertex + 2] = 0; currentVertex += 3; int n = y * (widthSegments + 1) + x; if (y < heightSegments && x < widthSegments) { // Face one indices[currentIndex] = (short) n; indices[currentIndex + 1] = (short) (n + 1); indices[currentIndex + 2] = (short) (n + w); // Face two indices[currentIndex + 3] = (short) (n + 1); indices[currentIndex + 4] = (short) (n + 1 + w); indices[currentIndex + 5] = (short) (n + 1 + w - 1); currentIndex += 6; } } } setIndices(indices); setVertices(vertices); } }
下一步我以爲應該建立一個立方體了。我將會建立一個簡單的立方體,你能夠設置寬高深,可是我建議你像咱們在建立平面時同樣操做,把這個當作一個練習。
構造方法像下面這樣:
public Cube(float width, float height, float depth)
因爲我沒有使用片元,因此構造方法會很簡單。
package se.jayway.opengl.tutorial.mesh; public class Cube extends Mesh { public Cube(float width, float height, float depth) { width /= 2; height /= 2; depth /= 2; float vertices[] = { -width, -height, -depth, // 0 width, -height, -depth, // 1 width, height, -depth, // 2 -width, height, -depth, // 3 -width, -height, depth, // 4 width, -height, depth, // 5 width, height, depth, // 6 -width, height, depth, // 7 }; short indices[] = { 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, 2, 6, 7, 2, 7, 3, 3, 7, 4, 3, 4, 0, 4, 7, 6, 4, 6, 5, 3, 0, 1, 3, 1, 2, }; setIndices(indices); setVertices(vertices); } }
若是你想要使用片元來作,那麼構造方法應該是這樣的:
public Cube(float width, float height, float depth, int widthSegments, int heightSegments, int depthSegments)
如今咱們有了Plane來替換Square類(教程二中的代碼),我刪除它,在Renderer類中把Square改爲Cube。
public OpenGLRenderer() { // 初始化cube cube = new Cube(1, 1, 1); cube.rx = 45; cube.ry = 45; }
而後渲染它:
public void onDrawFrame(GL10 gl) { ... // 繪製cube cube.draw(gl); }
」group「是很適合去初始化和控制3D場景的。group作的是分發全部的命令到它其中的子元素中。下面是group的簡單實現:
package se.jayway.opengl.tutorial.mesh; import java.util.Vector; import javax.microedition.khronos.opengles.GL10; public class Group extends Mesh { private Vector<mesh> children = new Vector<mesh>(); @Override public void draw(GL10 gl) { int size = children.size(); for( int i = 0; i < size; i++) children.get(i).draw(gl); } public void add(int location, Mesh object) { children.add(location, object); } public boolean add(Mesh object) { return children.add(object); } public void clear() { children.clear(); } public Mesh get(int location) { return children.get(location); } public Mesh remove(int location) { return children.remove(location); } public boolean remove(Object object) { return children.remove(object); } public int size() { return children.size(); } }
將cube添加到group中,並將group做爲根結點,交給renderer渲染。
Group group = new Group(); Cube cube = new Cube(1, 1, 1); cube.rx = 45; cube.ry = 45; group.add(cube); root = group;
渲染咱們的場景:
public void onDrawFrame(GL10 gl) { ... // Draw our scene. root.draw(gl); }
當你開始一個新項目的時候,建立一些基礎類是個不錯的主意。以個人經驗,當你開始編碼的時候,十次有九次,你沒有從美工那裏拿到任何能夠渲染的東西,因此保留一些網格做爲佔位符仍是不錯的。我會告訴你個人作法,這樣你能夠本身建立本身的基礎網格類庫。
建立你本身的網格類庫,也是瞭解頂點和渲染順序的好方式。
當你完成了立方體,而後我建議你去建立圓錐體。圓錐體並非簡單的圓錐體,若是你只建立了三四個面,那麼看起來可能像個金字塔形的東西,若是頂部和底部的半徑同樣,那就成了一個圓柱體。這就是爲何圓錐體這麼重要。以下圖,圓錐體可以作成什麼樣。
public Cone(float baseRadius, float topRadius, float height, int numberOfSides)
public class Pyramid extends Cone { public Pyramid(float baseRadius, float height) { super(baseRadius, 0, height, 4); } }
public class Cylinder extends Cone { public Cylinder(float radius, float height) { super(radius, radius, height, 16); } }
分割平面是應該須要瞭解的事,並且你如今已經知道了怎樣去分割一個規則的平面。要是分割一個以下圖的三角形,就有點不同了,可能也會比較難以實現。
這篇教程引用以下文獻:
你能夠下載教程的源碼:Tutorial_Part_V
你也能夠檢出代碼:code.google.com
上一篇教程:【翻譯】安卓opengl ES教程之四——添加顏色
下一篇教程:安卓 opengl ES教程之六——紋理