Chinaunix首页 | 论坛 | 博客
  • 博客访问: 547790
  • 博文数量: 179
  • 博客积分: 3845
  • 博客等级: 中校
  • 技术积分: 2003
  • 用 户 组: 普通用户
  • 注册时间: 2010-08-16 21:25
文章分类
文章存档

2012年(74)

2011年(105)

分类: 嵌入式

2012-01-17 12:31:44

一直没时间学习手机,以前写过一篇 OpenGL ES 的学习笔记,结果数据丢失。。。还好发现网上有朋友替我做了备份(排版丢失,花了点时间才所代码行分清楚)。这两天重新翻出了 eclipse 和 Android SDK,复习下基础先

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.leftcode.android.HelloWorld, 并勾选 Create Activity => Main。

在新建好的Project中, 打开 src/com.leftcode.android.HelloWorld 下的 Main.java,添加一个成员变量

修改 onCreate 方法,把 setContentView(…) 一行删除,替换为以下内容:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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以三角形为基本绘图单位,数据表示上,三角形由三个顶点组成。先定义些顶点

1
2
3
4
5
6
7
8
9
10
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的。 定义完顶点后,还要把这些顶点连成三形才能绘制,这里用顶点索引模式,所以接下来定义索引数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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使用,所以定义缓冲成员变量

1
2
private ByteBuffer vertices;
private ByteBuffer triangles;

和一个createBuffers方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void createBuffers() {
    // 创建顶点缓冲,顶点数组使用 float 类型,每个 float 长4个字节
    // 并设置字节顺序为本机顺序
    // 然后通过 FloatBuffer 适配器将 float 数组写入到 ByteBuffer 中
    vertices = ByteBuffer.allocateDirect(data_vertices.length * 4);
    vertices.order(ByteOrder.nativeOrder());
    vertices.asFloatBuffer().put(data_vertices);
    vertices.position(0); // 重置Buffer的当前位置
  
    // 创建索引缓冲,索引使用 byte 类型,所以无需设置字节顺序,也无需写入适配。
    triangles = ByteBuffer.allocateDirect(data_triangles.length * 2);
    triangles.put(data_triangles);
    triangles.position(0);
}

然后在 Renderer 的构造方法中调用 createBuffers 方法创建数据缓冲对象。

1
2
3
public HelloWorldRenderer(Main main) {
    createBuffers();
}

现在要绘制这个立方体了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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 控制每次绘制的角度

1
private float angle = 0f;

运行一下, 显示了一个旋转的立方体 — 实际上确切的说是一个绿乎乎平平的变化的多边形。这是因为 glColor 模式简单的将颜色填充在多边形中,没有层次,所以看起来一点也不立体, 来简单怎么改进下吧。glColor 设置所有的顶点使用一个颜色值,所以一切都是平的,来为顶点单独设置下颜色看看

1
2
3
4
5
6
7
8
9
10
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 成员变量

1
private ByteBuffer colors;

并修改createBuffers 添加以下内容

1
2
3
4
5
// 创建颜色缓冲
colors = ByteBuffer.allocateDirect(data_colors.length * 4);
colors.order(ByteOrder.nativeOrder());
colors.asFloatBuffer().put(data_colors);
colors.position(0);

修改绘制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 启用顶点数组、颜色数组
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);

将表示角度的成员变量angle 一分为三以让旋转看上去更有魅力:)

1
2
3
private float anglex = 0f;
private float angley = 0f;
private float anglez = 0f;

并修改旋转模型的代码为

1
2
3
4
5
6
// 将旋转矩阵应用到当前矩阵堆栈上,即旋转模型
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;

运行发现在旋转过程中方块有的面出现缺失,这是因为绘制三角形的顺序问题,如果先绘制了前边的,后边的三角形在绘制时会把前边的覆盖。 这个问题可以使用深度测试来解决。 修改 onSurfaceCreated 添加以下代码启用深度测试

1
2
3
gl.glEnable(GL10.GL_DEPTH_TEST); // 开启深度测试
gl.glDepthFunc(GL10.GL_LEQUAL); // 尝试测试方法为小于等于
gl.glClearDepthf(1f); // 清除缓冲时要用的值

再次运行,效果好多了(虚拟机截图):

最终代码:
 android-opengles-1.zip   

阅读(2662) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~