【翻譯】安卓openGL ES教程之五——關於網格的更多事

我有一個預感,看了我前幾篇教程的人,可能會問我:這是一系列3D教程,爲何講的都是2D的事呢?那麼在接下來這篇教程中,咱們來建立一些3D的渲染網格。這也是後面的教程所須要的準備。
java

在當初我開始學習openGL的時候,我也很困惑如何用編程方式取實現立方體,圓錐體等等。我想要它很容易的可以被集成到個人場景中。因此這篇教程將會講解如何建立一些初級的立體模型。這可能不是效率最高的方式,可是確實是可以實現的一種方式。
android

設計

設計一個openGL 框架之初,最好是繪製組合圖。以下是我如何開始的示意圖:編程

讓咱們開始製造這些組合吧。
框架

Mesh

爲渲染的網格建立一個基礎類是個不錯的主意。就讓咱們從建立一個叫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

」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);
    }
}

還有一件事

分割平面是應該須要瞭解的事,並且你如今已經知道了怎樣去分割一個規則的平面。要是分割一個以下圖的三角形,就有點不同了,可能也會比較難以實現。

引用

這篇教程引用以下文獻:

Android Developers

OpenGL ES 1.1 Reference Pages

你能夠下載教程的源碼:Tutorial_Part_V

你也能夠檢出代碼:code.google.com

上一篇教程:【翻譯】安卓opengl ES教程之四——添加顏色

下一篇教程:安卓 opengl ES教程之六——紋理

相關文章
相關標籤/搜索