程序更新与Android2.0.1兼容。
如果你是初次接触这个系列文章,请先阅读第一部分。
这个系列的第五部分将讲述如何创建一个3D的物体。本实例中是一个具有四个面的三角锥。
为了使我们今后的开发更简单,需要做一些准备。我们需要能够动态地计算缓冲区大小和创建数组。
private int _nrOfVertices = 0;
private void initTriangle() { float[] coords = { // 坐标 };
_nrOfVertices = coords.length;
float[] colors = { // 颜色 };
short[] indices = new short[] { // 索引 };
// float has 4 bytes, coordinate * 4 bytes
ByteBuffer vbb = ByteBuffer.allocateDirect(coords.length * 4); vbb.order(ByteOrder.nativeOrder()); _vertexBuffer = vbb.asFloatBuffer();
// short has 2 bytes, indices * 2 bytes
ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2); ibb.order(ByteOrder.nativeOrder()); _indexBuffer = ibb.asShortBuffer();
// float has 4 bytes, colors (RGBA) * 4 bytes
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4); cbb.order(ByteOrder.nativeOrder()); _colorBuffer = cbb.asFloatBuffer();
_vertexBuffer.put(coords); _indexBuffer.put(indices); _colorBuffer.put(colors);
_vertexBuffer.position(0); _indexBuffer.position(0); _colorBuffer.position(0); }
为了更动态一些,我们需要改变一些变量:
在第一行可以看到我们给_nrOfVertices的初值为0因为它的值取决于第七行坐标数组的大小。另外我们将成员变量_indiceArray改为局部变量,该局部变量叫做indices,在第十三行初始化。
缓冲区的创建移到了坐标数组,颜色数组和索引数组下面,因为缓冲区的大小依赖于这些数组的大小。请阅读相关的第17~18行,第22~23行,第27~28行,在注释中我解释清楚了其中的数学表示含义。
这样修改的好处是,我们可以创建更多的顶点而不必手动来修改关于顶点数量,数组和缓冲区大小等值。
下一步,你必须理解OpenGL是如何绘制我们所看到的图形的。OpenGL ES相比OpenGL的一个缺陷就是它只支持三角形。因此为了创建多边形我们必须由三角形来搭建。
下面我将引用来自IPhone developer的一篇博客,同时也要推荐他的OpenGL ES series相关文章。
有一些关于绘制三角形的事情我们必须了解。在OpenGL中,有一个围绕(winding)的概念,即点被绘制的顺序是重要的。不像真实世界中的物体,OpenGL中的多边形没有两个面。他们只有一个面,即前面,如果一个三角形可以被看到的话,那么它的前面应该面对着观察者。虽然使得OpenGL能够将多边形看作是双面的是可能的,但是默认情况下,三角形只有一个可以被观察到的面。通过了解多边形的哪一面是可以被观察到的,OpenGL就可以只做一半的计算量就将多边形绘制出来。
虽然许多情况下多边形是独立的,你可能希望能够绘制它的背面。通常情况下三角形是一个物体的一部分,三角形的一面将面对这物体的内部并且这一面不会被观察到。这个不需要绘制的面就可以称之为背面,OpenGL通过绘制这个图形的点的顺序来判断哪一面是前面哪一面是背面。如果绘制图形的点的顺序是逆时针的,那么这个面就是朝外的面(可以修改)。既然OpenGL可以容易的决定哪个三角形是可以被观察到的,它可以利用一个叫做Backface Culling的进程来避免绘制并不需要在视窗中被观察到的多边形。下一个文章我会讨论视窗,当然你可以认为它是一个虚拟的窗口来帮助你观察OpenGL的世界。
在上面的演示中,左边的蓝绿色的三角形就是一个背面因为它的顶点的绘制顺序对于观察者来说是顺时针的。而右边的三角形就是一个前面因为对于观
察者来说它的顶点绘制顺序是逆时针的。
现在我们想要创建一个多彩的三角锥,首先需要使依赖我们触摸事件的glClearColor()函数不再工作。因此我们需要移除变量_red, _green, _blue以及setColor()函数。
我们也要改变转动的方向,因此我们把旋转分解到x,y两个轴上。
public class VortexRenderer implements GLSurfaceView.Renderer {
private static final String LOG_TAG = VortexRenderer.class.getSimpleName();
// 一个缓冲区来保存绘图顶点的索引值 private ShortBuffer _indexBuffer;
// 一个缓冲区来保存顶点坐标 private FloatBuffer _vertexBuffer;
// 一个缓冲区来存储颜色
private FloatBuffer _colorBuffer;
private int _nrOfVertices = 0;
private float _xAngle; private float _yAngle;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) { // code snipped }
@Override
public void onSurfaceChanged(GL10 gl, int w, int h) { gl.glViewport(0, 0, w, h); }
public void setXAngle(float angle) { _xAngle = angle; }
public float getXAngle() { return _xAngle;
}
public void setYAngle(float angle) { _yAngle = angle; }
public float getYAngle() { return _yAngle; }
// code snipped
确保你的工程中有与我在类的开始定义的相同名称的变量。这里有两个float型变量来记录角度,xAngle,和_yAngle以及它们相应的设置函数以及获取函数。
// code snipped
private float _x = 0; private float _y = 0;
// code snipped
public boolean onTouchEvent(final MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { _x = event.getX(); _y = event.getY(); }
if (event.getAction() == MotionEvent.ACTION_MOVE) { final float xdiff = (_x - event.getX()); final float ydiff = (_y - event.getY()); queueEvent(new Runnable() { public void run() {
_renderer.setXAngle(_renderer.getXAngle() + ydiff); _renderer.setYAngle(_renderer.getYAngle() + xdiff); } });
_x = event.getX(); _y = event.getY(); }
return true; }