【翻译】安卓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教程之六——纹理

相关文章
相关标签/搜索