Android OpenGL ES (1) -- 基础

来源:互联网 发布:腾讯视频for mac 编辑:程序博客网 时间:2024/05/16 19:18
 

Android 上开发三维图形程序主要使用 OpenGL ES 接口了, 这个OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 的子集,主要针对移动设备。该API目前由 Khronos 维护,Khronos是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准。

这个 OpenGL ES 的接口和OpenGL一样,C风格的定义,说起来比 DirectX 的 C++ 风格要简洁明了, 但弄到面向对像的 Java 里来 -- 说实话 -- 就有点不伦不类的样子了, 尤其Java 一没指针二不支持引用传参, 所以看到用数组参数返回值的情况也不奇怪了。

首先建立一个Android OpenGL 程序的框架,主要用 GLSurfaceView 类及 Renderer 接口的实现类。 在 Eclipse 中新建一个Android Project, Project Name => HelloWorld, Package name => com.leftart.android.HelloWorld, 并勾选 Create Activity => Main。

在新建好的Project中, 打开 src/com.leftart.android.HelloWorld 下的 Main.java, 修改 onCreate 方法,把 setContentView(...) 一行删除,替换为以下内容:

        /* 设置窗体为全屏模式,无标题 */        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);        getWindow().requestFeature(Window.FEATURE_NO_TITLE);         /* 创建一个 GLSurfaceView 用于绘制表面 */        GLSurfaceView view = new GLSurfaceView(this);         /* 设置 Renderer 用于执行实际的绘制工作 */        view.setRenderer(new HelloWorldRenderer(this));         /* 设置绘制模式为 持续绘制  */        view.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);         /* 将创建好的 GLSurfaceView 设置为当前 Activity 的内容视图 */        setContentView(view);

接下来要定义 HelloWorldRenderer 类了, 这个类将 GLSurfaceView.Renderer 接口。

public class HelloWorldRenderer implements Renderer{    public HelloWorldRenderer(Main main)    {    }     public void onDrawFrame(GL10 gl)    {        // 清除颜色缓冲区背景        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);    }     public void onSurfaceChanged(GL10 gl, int width, int height)    {        // 宽高比        float aspect = (float)width / (float)(height == 0 ? 1 : height);         // 设置视口        gl.glViewport(0, 0, width, height);         // 设置当前矩阵堆栈为投影矩阵,并将矩阵重置为单位矩阵        gl.glMatrixMode(GL10.GL_PROJECTION);        gl.glLoadIdentity();         /* 对当前矩阵应用透视投影变换,这个GL辅助方法以非常直观的参数         * 来设置投影矩阵:设眼睛的座标为原点,眼睛朝向Z坐标轴负方向,         * 以Y坐标轴正方向为上方,视野在水平(X-Z平面)方向上角度由参数         * fovy指定,而参数 aspect 指定视野垂直方向与水平方向的比率。         * 后面两个参数分别指定眼睛可以看到前边的最近距离和最远距离。 */        GLU.gluPerspective(gl, 45.0f, aspect, 0.1f, 200.0f);         /* 变换当前的透视投影矩阵,该辅助方法假设当前眼睛位于原点并朝向Z轴         * 负方向, 应用 gluLookAt 后,眼睛的位置移动到了参数 {eyeX, eyeY, eyeZ}         * 所表示的三维空间点, 并调整视线方向直视三个 center* 参数所示的点。         * 最后三个参数构成的向量表示正上方。 */        GLU.gluLookAt(gl, 5f, 5f, 5f, 0f, 0f, 0f, 0, 1, 0);    }     public void onSurfaceCreated(GL10 gl, EGLConfig config)    {        gl.glDisable(GL10.GL_DITHER); // 颜色抖动据说可能严重影响性能,禁用        gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// 设置清除颜色缓冲区时用的RGBA颜色值    }}
现在我们已经定义好了一个OpenGL应用程序的基本部分, 这个程序在启动时创建GLSurfaceView作为主Activity的内容, 设置HelloWorldRenderer为绘图器对象,在表面创建时禁用颜色抖动,并设置颜色缓冲清除颜色为黑色。在表面尺寸变化时, 设置视口和投影矩阵。在绘制图形时仅仅清除颜色缓冲区。 现在运行程序,显示漆黑一片!

来加点形状吧。OpenGL以三角形为基本绘图单位,数据表示上,三角形由三个顶点组成。先定义些顶点吧。
private float[] data_vertices = {1, 1, 1,  1, -1, 1,-1, -1, 1,-1, 1, 1,1, 1, -1,  1, -1, -1,-1, -1, -1,-1, 1, -1,};
这是一个立方体的8个顶点的数据,顶点可以是二、三、四维的,这里用的三维形式。数据类型为float,也可以是integer的。 定义完顶点后,还要把这些顶点连成三形才能绘制,这里用顶点索引模式,所以接下来定义索引数据
private byte[] data_triangles = {0, 1, 2,0, 2, 3,0, 3, 7,0, 7, 4,0, 4, 5,0, 5, 1,6, 5, 4,6, 4, 7,6, 7, 3,6, 3, 2,6, 2, 1,6, 1, 5};
这里定义了构成立方体的12个三角形(6个表面,每个表面2个三角形)。 在有了顶点数据和索引数据后, 还要把数据装入缓冲对像中才能被OGL使用,所以定义缓冲成员变量和一个createBuffers方法
private ByteBuffer vertices;private ByteBuffer triangles; private void createBuffers(){    // 创建顶点缓冲,顶点数组使用 float 类型,每个 float 长4个字节    vertices = ByteBuffer.allocateDirect(data_vertices.length * 4);    // 设置字节顺序为本机顺序    vertices.order(ByteOrder.nativeOrder());    // 通过一个 FloatBuffer 适配器,将 float 数组写入 ByteBuffer 中    vertices.asFloatBuffer().put(data_vertices);    // 重置Buffer的当前位置    vertices.position(0);     // 创建索引缓冲,索引使用 byte 类型,所以无需设置字节顺序,也无需写入适配。    triangles = ByteBuffer.allocateDirect(data_triangles.length);    triangles.put(data_triangles);    triangles.position(0);}
然后在 Renderer 的构造方法中调用 createBuffers 方法创建数据缓冲对象。
    public HelloWorldRenderer(Main main)    {        createBuffer();    }
现在要绘制这个立方体了
    public void onDrawFrame(GL10 gl)    {        // 清除颜色缓冲        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);         // 设置当前矩阵堆栈为模型堆栈,并重置堆栈,        // 即随后的矩阵操作将应用到要绘制的模型上        gl.glMatrixMode(GL10.GL_MODELVIEW);        gl.glLoadIdentity();         // 将旋转矩阵应用到当前矩阵堆栈上,即旋转模型        gl.glRotatef(angle, 1, 1, 1);        angle += 0.1; // 递增角度值以便每次以不同角度绘制         // 设置颜色,模型将以此颜色绘制        gl.glColor4f(0, 1f, 0f, 1f);         // 启用顶点数组        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);         // 设置顶点数组指针为 ByteBuffer 对象 vertices         // 第一个参数为每个顶点包含的数据长度(以第二个参数表示的数据类型为单位)        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertices);        // 绘制 triangles 表示的三角形        gl.glDrawElements(GL10.GL_TRIANGLES, triangles.remaining(), GL10.GL_UNSIGNED_BYTE, triangles);         // 禁用顶点数组        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);    }

其中用了一个成员变量angle 控制每次绘制的角度
private float angle = 0f;
运行一下, 显示了一个旋转的立方体 -- 实际上确切的说是一个绿乎乎平平的变化的多边形。这是因为 glColor 模式简单的将颜色填充在多边形中,没有层次,所以看起来一点也不立体, 来简单怎么改进下吧。glColor 设置所有的顶点使用一个颜色值,所以一切都是平的,来为顶点单独设置下颜色看看
private float[] data_colors = { 1, 1, 0, 1,1, 0, 1, 1,0, 1, 1, 1,1, 0, 0, 1,0, 0, 0, 1,0, 0, 1, 1,0, 1, 0, 1,1, 1, 1, 1,}
以上是8个顶点的8个颜色值,也要用 ByteBuffer 传递给GL,添加 名为colors 的ByteBuffer 成员变量
private ByteBuffer colors;

并修改createBuffers 添加以下内容
colors = ByteBuffer.allocateDirect(data_colors.length * 4);colors.order(ByteOrder.nativeOrder());colors.asFloatBuffer().put(data_colors);colors.position(0);
修改绘制代码
        // 启用顶点数组、法向量、颜色数组        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);        gl.glEnableClientState(GL10.GL_COLOR_ARRAY);         // 设置顶点数组指针为 ByteBuffer 对象 vertices        // 第一个参数为每个顶点包含的数据长度(以第二个参数表示的数据类型为单位)        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertices);        gl.glColorPointer(4, GL10.GL_FLOAT, 0, colors);         // 绘制 triangles 表示的三角形        gl.glDrawElements(GL10.GL_TRIANGLES, triangles.remaining(),                GL10.GL_UNSIGNED_BYTE, triangles);         // 禁用顶点、法向量、颜色数组        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);        gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
为方便观察修改一下旋转方块的代码
        // 将旋转矩阵应用到当前矩阵堆栈上,即旋转模型        gl.glRotatef(anglez, 0, 0, 1);        gl.glRotatef(angley, 0, 1, 0);        gl.glRotatef(anglex, 1, 0, 0);        anglex += 0.1; // 递增角度值以便每次以不同角度绘制        angley += 0.2;        anglez += 0.3;
并将成员变量
private float angle = 0f;
修改为
    private float anglex = 0f;    private float angley = 0f;    private float anglez = 0f;
运行发现在旋转过程中方块有的面出现缺失,这是因为绘制三角形的顺序问题,如果先绘制了前边的,后边的三角形在绘制时会把前边的覆盖。 这个问题可以使用深度测试来解决。 修改 onSurfaceCreated 启用深度测试
        // 启用深度测试        gl.glEnable(GL10.GL_DEPTH_TEST);        gl.glDepthFunc(GL10.GL_LEQUAL); // 深度测试方法为小于等于(时绘制)        gl.glClearDepthf(1f); // 清除深度缓冲区时使用的值
再次运行,效果好多了。
最终代码:
/**** file : Main.java ****/package com.leftart.android.HelloWorld; import android.app.Activity;import android.opengl.*;import android.os.Bundle;import android.view.*; public class Main extends Activity { GLSurfaceView view; /** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); /* 设置窗体为全屏模式,无标题 */getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);getWindow().requestFeature(Window.FEATURE_NO_TITLE); /* 创建一个 GLSurfaceView 用于绘制表面 */GLSurfaceView view = new GLSurfaceView(this); /* 设置 Renderer 用于执行实际的绘制工作 */view.setRenderer(new HelloWorldRenderer(this)); /* 设置绘制模式为 持续绘制 */view.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); /* 将创建好的 GLSurfaceView 设置为当前 Activity 的内容视图 */setContentView(view);}}
/**** file : HelloWorldRenderer.java ****/package com.leftart.android.HelloWorld; import java.nio.ByteBuffer;import java.nio.ByteOrder; import javax.microedition.khronos.egl.EGLConfig;import javax.microedition.khronos.opengles.GL10; import android.opengl.GLSurfaceView.Renderer;import android.opengl.GLU; public class HelloWorldRenderer implements Renderer {public HelloWorldRenderer(Main main) {createBuffers();}  public void onSurfaceCreated(GL10 gl, EGLConfig config) {gl.glDisable(GL10.GL_DITHER); // 颜色抖动据说可能严重影响性能,禁用gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// 设置清除颜色缓冲区时用的RGBA颜色值gl.glEnable(GL10.GL_DEPTH_TEST);gl.glDepthFunc(GL10.GL_LEQUAL);gl.glClearDepthf(1f);}public void onSurfaceChanged(GL10 gl, int width, int height) {// 宽高比float aspect = (float) width / (float) (height == 0 ? 1 : height); // 设置视口gl.glViewport(0, 0, width, height); // 设置当前矩阵堆栈为投影矩阵,并将矩阵重置为单位矩阵gl.glMatrixMode(GL10.GL_PROJECTION);gl.glLoadIdentity(); GLU.gluPerspective(gl, 45.0f, aspect, 0.1f, 200.0f);GLU.gluLookAt(gl, 5f, 5f, 5f, 0f, 0f, 0f, 0, 1, 0);}public void onDrawFrame(GL10 gl) {// 清除颜色缓冲gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 设置当前矩阵堆栈为模型堆栈,并重置堆栈,// 即随后的矩阵操作将应用到要绘制的模型上gl.glMatrixMode(GL10.GL_MODELVIEW);gl.glLoadIdentity();gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, new float[]{5, 5, 5, 1}, 0); // 将旋转矩阵应用到当前矩阵堆栈上,即旋转模型gl.glRotatef(anglez, 0, 0, 1);gl.glRotatef(angley, 0, 1, 0);gl.glRotatef(anglex, 1, 0, 0);anglex += 0.1; // 递增角度值以便每次以不同角度绘制angley += 0.2;anglez += 0.3;// 启用顶点数组、法向量、颜色数组gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);gl.glEnableClientState(GL10.GL_COLOR_ARRAY); // 设置顶点数组指针为 ByteBuffer 对象 vertices// 第一个参数为每个顶点包含的数据长度(以第二个参数表示的数据类型为单位)gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertices);gl.glColorPointer(4, GL10.GL_FLOAT, 0, colors);// 绘制 triangles 表示的三角形gl.glDrawElements(GL10.GL_TRIANGLES, triangles.remaining(),GL10.GL_UNSIGNED_BYTE, triangles); // 禁用顶点、法向量、颜色数组gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);gl.glDisableClientState(GL10.GL_COLOR_ARRAY);} private void createBuffers() {// 创建顶点缓冲,顶点数组使用 float 类型,每个 float 长4个字节vertices = ByteBuffer.allocateDirect(data_vertices.length * 4);// 设置字节顺序为本机顺序vertices.order(ByteOrder.nativeOrder());// 通过一个 FloatBuffer 适配器,将 float 数组写入 ByteBuffer 中vertices.asFloatBuffer().put(data_vertices);// 重置Buffer的当前位置vertices.position(0); // 创建索引缓冲,索引使用 byte 类型,所以无需设置字节顺序,也无需写入适配。triangles = ByteBuffer.allocateDirect(data_triangles.length * 2);triangles.put(data_triangles);triangles.position(0);colors = ByteBuffer.allocateDirect(data_colors.length * 4);colors.order(ByteOrder.nativeOrder());colors.asFloatBuffer().put(data_colors);colors.position(0);}private ByteBuffer vertices;private ByteBuffer triangles;private ByteBuffer colors;     private float anglex = 0f;    private float angley = 0f;    private float anglez = 0f; private float[] data_vertices = { 1, 1, 1, 1, -1, 1, -1, -1, 1, -1, 1, 1,1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, };private float[] data_colors = { 1, 1, 0, 1,1, 0, 1, 1,0, 1, 1, 1,1, 0, 0, 1,0, 0, 0, 1,0, 0, 1, 1,0, 1, 0, 1,1, 1, 1, 1,};private byte[] data_triangles = { 0, 1, 2, 0, 2, 3, 0, 3, 7, 0, 7, 4, 0, 4, 5, 0, 5, 1,6, 5, 4, 6, 4, 7, 6, 7, 3, 6, 3, 2, 6, 2, 1, 6, 1, 5 };}
原创粉丝点击