【翻译】安卓opengl ES教程之二——创建多边形

在上一篇教程中我们讨论了关于初始化一个GLSurfaceView。在我们开始新的内容之前,请确保你已经读了它。

创建多边形

在这篇教程中,我们将要渲染我们的第一个多边形。

3D模型是由许多可以被单独操作的小元素(顶点,边,面和多边形)组成的;

顶点

一个顶点vertex(复数vertices)是3D模型的最小单元。顶点是由两条或者多条边相交形成的点。在3D模型中,顶点可以被所有相连的边,面,多边形共享。一个顶点也可以代表相机或者光源的位置。你可以在下图中看到一个标记为黄色的顶点。

【翻译】安卓opengl ES教程之二——创建多边形_第1张图片

我们通过定义一个float数组,并将其放进一个bytebuffer中以提高性能的方式来定义一组顶点。如下图,我们标记的顶点对应到下面的代码中:

【翻译】安卓opengl ES教程之二——创建多边形_第2张图片

private float vertices[] = {
      -1.0f,  1.0f, 0.0f,  // 0, 左上
      -1.0f, -1.0f, 0.0f,  // 1, 左下
       1.0f, -1.0f, 0.0f,  // 2, 右下
       1.0f,  1.0f, 0.0f,  // 3, 右上
};

// 一个float占用4字节,所以我们申请大小时要把顶点数组的长度乘以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我们已经创建好了顶点缓冲区,并告诉它缓冲区的位置。

// 为渲染过程中开启顶点数组的读写
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// 指定渲染过程中顶点数据的位置和格式
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);

当你处理完这些buffer的时候,别忘了禁用顶点数组。

// 禁用顶点数组
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);


边是两个顶点之间的连线。他们是面和多边形的边框。在3D模型中,一条边可以被两个面或者多边形共有。对一个边进行变换,会影响所有的相接的点,面,多边形。在opengl ES中,你不必定义边,而应该通过给定一组顶点来定义一个面,从而建立三条边。如果你想要修改一条边,你应该修改组成这条边的两个顶点。如下图,你可以看到一条被标记为黄色的边。

【翻译】安卓opengl ES教程之二——创建多边形_第3张图片


面是一个三角形。面是由三个角的顶点和他们的边围成的区域。对一个面的变换,影响所有相接的顶点,边和多边形。

【翻译】安卓opengl ES教程之二——创建多边形_第4张图片

顺序很重要

绘制平面的方向是很重要的,因为这个方向决定了哪一面是“前面”,哪一面是“背面”。之所以这点很重要,是因为出于性能考虑,我们不需要去同时绘制两个面,我们需要背面裁剪。所以使用同样的解析是个不错的办法。这样就可能使得通过glFrontFace定义的“前面”的方向可以被改变。

 gl.glFrontFace(GL10.GL_CCW);

当然也可以改变哪个面被绘制或者不被绘制。

 gl.glCullFace(GL10.GL_BACK);

多边形

【翻译】安卓opengl ES教程之二——创建多边形_第5张图片

是时候解析平面了,记住我们使用默认的方向——逆时针。看下图和代码解释了如何去建立一个四边形。

【翻译】安卓opengl ES教程之二——创建多边形_第6张图片

private short[] indices = { 0, 1, 2, 0, 2, 3 };

为了提升一点性能,我们把这个数组放到一个byteBuffer中:

// short是两个字节,所以应该是数组长度乘以2
ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
ibb.order(ByteOrder.nativeOrder());
ShortBuffer indexBuffer = ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);

渲染

现在我们可以在屏幕上绘制些东西了。这有两个函数可以用来绘制,我们需要决定一下使用哪一个。

这两个函数分别是:

public abstract void glDrawArrays(int mode, int first, int count)

glDrawArrays会按照我们在顶点缓冲里指定的顺序绘制顶点们。

public abstract void glDrawElements(int mode, int count, int type,
                                    Buffer indices)

glDrawElements需要多一点的信息才能绘制。他需要知道顶点的绘制顺序,还需要知道顶点的顺序缓冲indicesbuffer。

由于我们已经定义了indicesBuffer,所以我想你应该知道我们要使用哪个方法了。

这两个方法的相同之处在于他们都需要知道要绘制什么,即最原始的东西(图元)。由于绘制这些顶点的方式也有好几种,而出于我们对程序的调试需要,最好也了解一下。下面我将会介绍几种。

图元类型

GL_POINTS

在屏幕上绘制独立的点

【翻译】安卓opengl ES教程之二——创建多边形_第7张图片

GL_LINE_STRIP

    一系列连起来的线段

【翻译】安卓opengl ES教程之二——创建多边形_第8张图片

GL_LINE_LOOP

和上面的类似,但是多了一条连起起点和终点的线(封闭)

【翻译】安卓opengl ES教程之二——创建多边形_第9张图片

GL_LINES

每两个点连接成线段,互相独立。

【翻译】安卓opengl ES教程之二——创建多边形_第10张图片

GL_TRIANGLES

每三个点组成一个三角形。

【翻译】安卓opengl ES教程之二——创建多边形_第11张图片

GL_TRIANGLE_STRIP

按照顶点先v0,v1,v2,然后v2,v1,v3(注意顺序),接着v2,v3,v4。。。,绘制一系列的三角形。这个顺序是保证所有的三角形都是按照同一个方向绘制出来的,这样这些三角形就能正确的组成一个平面的一部分。

【翻译】安卓opengl ES教程之二——创建多边形_第12张图片

GL_TRIANGLE_FAN

和上面类似,除了绘制顺序变成v0,v1,v2,然后是v0,v2,v3,接下来是v0,v3,v4。。。。

【翻译】安卓opengl ES教程之二——创建多边形_第13张图片

我认为GL_TRIANGLES是最容易的,所以接下来我们就使用它了。

组装代码

现在让我们把四边形的代码放到一个类中。

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 {
	// 顶点.
	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
		};

	// 顶点连接的顺序
	private short[] indices = { 0, 1, 2, 0, 2, 3 };

	// 顶点缓冲.
	private FloatBuffer vertexBuffer;

	// 顺序缓冲.
	private ShortBuffer indexBuffer;

	public Square() {
		// 一个float是4字节,所以我们申请缓冲的大小为数组长度乘以4
		ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
		vbb.order(ByteOrder.nativeOrder());
		vertexBuffer = vbb.asFloatBuffer();
		vertexBuffer.put(vertices);
		vertexBuffer.position(0);

		// 一个short是两个字节,申请缓冲大小为数组长度乘以2
		ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
		ibb.order(ByteOrder.nativeOrder());
		indexBuffer = ibb.asShortBuffer();
		indexBuffer.put(indices);
		indexBuffer.position(0);
	}

	/**
	 * 绘制我们的四边形到屏幕上
	 * @param gl
	 */
	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,
                                 vertexBuffer);

		gl.glDrawElements(GL10.GL_TRIANGLES, indices.length,
				  GL10.GL_UNSIGNED_SHORT, indexBuffer);

		// 禁用顶点数组
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		// 禁用裁剪
		gl.glDisable(GL10.GL_CULL_FACE);
	}

}

我们需要在Renderer中初始化我们的square类。

// 初始化square.
Square square = new Square();

然后在绘制方法中调用square的绘制方法:

public void onDrawFrame(GL10 gl) {
		// 清除平面和深度缓存
		gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
                           GL10.GL_DEPTH_BUFFER_BIT);

		// 绘制四边形
		square.draw(gl); // ( NEW )
}

如果你现在运行这个程序,会发现平面还是一片漆黑。为什么?因为opengl ES渲染时默认从当前位置渲染,默认是(0,0,0),和视口(这里为眼睛或者镜头最好-译者注)位置一致。而且opengl ES不会渲染离视口很近的东西。解决方法是在绘制之前,先把绘图位置向屏幕里面移动一点。

// 向屏幕里移动4个单位
gl.glTranslatef(0, 0, -4);

在下一篇教程中,我会介绍不同的平移方式。

再次运行程序,你会看到这个四边形,但是很快就向远处移动直到消失。opengl ES不会重置两帧之间的绘制点,所以你必须自己来做这事。

// 替换当前的矩阵为初始矩阵
gl.glLoadIdentity();

现在你运行这个应用可以看到四边形在固定的位置上了。

引用

这篇教程引用如下文献:

Android Developers

OpenGL ES 1.1 Reference Pages

你可以下载教程的源码:Tutorial_Part_II

你也可以检出代码:code.google.com

前一篇教程:安卓opengl ES教程之一——初始化view

后一篇教程:安卓opengl ES教程之三——变换

你可能感兴趣的:(【翻译】安卓opengl ES教程之二——创建多边形)