sq_Hayden's blog

OpenGL ES实现3D星空背景旋转立方体

2018/05/16 Share

OpenGL ES实现3D星空背景旋转立方体

  • 引言:在androi开发中,如果需要去实现某些3D绘制效果,那就不得不提到OpenGL ES,利用它我们可以较完美的完成多种3D图形的绘制操作,以及关于3D的动画效果实现。

本次的目标,是实现如下所示的3D绘制效果,怎么样去做呢?

activity

OpenGL ES 简介

OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,为针对手机、PDA和游戏主机等嵌入式设备而设计。Android包括支持高性能2d和3d图形以及开放图形库(OpenGL),具体来说,即OpenGL是一个跨平台图形API,用于指定一个标准为3 d图形处理硬件的软件接口。

OpenGL ES 方法介绍

在Android平台上实现OpenGL的view绘制其实很简单,Android为我们提供了两个类来完成图像相关的绘制工作,那就是GLSurfaceViewGLSurfaceView.Renderer

  • GLSurfaceView
    这是一个视图类,GLSurfaceView是用来连接OpenGL ES和android本身的view结构的。一般来说,我们需要去继承它并在其构造函数中,进行图形相关参数的初始化操作。

那么,如何去引用呢?类似这样:

1
2
3
4
5
1.创建自定义视图类,用于UI展示
public class MySurfaceView extends GLSurfaceView {}
2.进行UI的Set操作
MySurfaceView mGLSurfaceView = new MySurfaceView(this);
setContentView(mGLSurfaceView);

  • public void setRenderer(GLSurfaceView.Renderer renderer)
    Renderer也叫渲染器,是一个接口,位于GLSurfaceView类中。我们同样也需要去自定义一个类实现它的三大方法,从而管理整个3D视图的渲染周期。
    • onSurfaceCreated():创建GLSurfaceView时回调。使用这个方法来执行操作,只需要发生一次,如设置OpenGL环境参数或初始化OpenGL图形对象。也会设置一些绘制时不常变化的参数,比如:背景色,是否打开 z-buffer等。
    • onDrawFrame():每绘制一帧的时由系统自动回调执行,在底层的渲染周期内,将会一直持续调用此方法来实现图像的动态改变。
    • onSurfaceChanged():GLSurfaceView几何变化时回调,包括GLSurfaceView大小的变化或设备的屏幕变换。

我们需要将自定义的渲染器对象Renderer在MySurfaceView对象创建时,通过构造器来完成绑定:

1
2
3
4
//对象传递
setRenderer(mRenderer);
//设置为自动模式以完成系统在帧绘制时,持续调用Renderer的onDrawFrame()方法
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

OpenGL ES 正方体绘制

一般来说,绘制一个正方体,我们只需要知道其坐标原点以及八个顶点坐标即可。在本次绘制中,我们采用一个int数组用来存储构成正方体六个面的24个三维点坐标,同时采用一种规则使它们联系起来,拼接成一个完全的正方体。

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
//坐标代码(等价于1.0f)
private int one = 0x00010000;
// 正方体顶点
private int[] vertices = {
【!!!此处的点坐标必须是有序的,具体规则参考下面描述!!!】
//右
one,-one,one,
one,one,one,
one,-one,-one,
one,one,-one,
//左
-one,-one,one,
-one,one,one,
-one,-one,-one,
-one,one,-one,
//下
-one,-one,one,
one,-one,one,
-one,-one,-one,
one,-one,-one,
//上
one,one,one,
-one,one,one,
one,one,-one,
-one,one,-one,
//前
-one,-one,one,
-one,one,one,
one,-one,one,
one,one,one,
//后
-one,-one,-one,
-one,one,-one,
one,-one,-one,
one,one,-one
};

在本次的点坐标数组排序中,0x10000是出于OPENGL前期内存节约的考虑,以INT型模拟FLOAT型来表示,0x 0001 0000 前面4位表示小数点前,后4位表示小数点后,所以0x10000表示浮点数的1.0f。

  • GL_TRIANGLE_STRIP模式介绍
    在OPenGL ES 中,面的绘制并不是由一个四点平面进行绘制的,而是由多个三角形依照不同规则进行拼接,用以形成一个平面。此处我们采用GL_TRIANGLE_STRIP模式,来完成正方的六个面绘制。
    activity
    如上图所示,假设此正方形是物体的上表面,且已知对应四个坐标点,那么为了避免重复绘制或者乱序绘制,我们需要对这四个三维坐标进行排序定义,之后调用GL_TRIANGLE_STRIP模式自动处理,就可以绘制出上表面。
    GL_TRIANGLE_STRIP模式即连续三角绘制,意味着它总是以连续的三个点绘制出一个三角形。
    此处若定义为{v0,v1,v2,v3},则会按照{0,1,2}和{1,2,3}绘制出两个三角形,且正好拼凑出上表面。假设我们定义为{v0,v1,v3,v2},则按照{0,1,3}和{1,3,2}绘制出了一个互相覆盖的多边形导致绘制失败。
    因此,我们在定义六个面的顶点坐标时,需要遵循一个规则:若以任一点作为起始绘制点,则其对角点必须作为最后一个定义点。

好了,定义好了坐标,我们就可以观察到一个正方体出现在了我们的视野中。不过此时由于遮挡效果,我们只能看到一个2D的平面图形,为了好看一些,先给它加入颜色索引,并对其进行三维的自旋操作,此时就可以较为清晰地观察到它的状态了:

activity

既然已经看到了效果,接下来我们就直接上代码,来完成后续的纹理添加以及背景绘制,因为代码中注释很多,在此就不多做说明了。

模块1–MySurfaceView的构造

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
public MySurfaceView(Context context) {
super(context);
//传入定义的渲染器对象
mRenderer = new SceneRenderer();
setRenderer(mRenderer);
//设置渲染模式为连续渲染
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
//初始化
textureids = new int[1];
//实例化bitmap
bitmap = BitGL.bitmap;
/*创建顶点坐标数据缓存,由于不同平台字节顺序不同,数据单元不是字节的,因此一定要经过ByteBuffer转换,关键是通过ByteOrder设置nativeOrder()*/
//---------------------------------------------------
//缓存顶点数组
//分配的内存块
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
//设置本地平台的字节顺序
vbb.order(ByteOrder.nativeOrder());
//转换为int型缓冲
vertexBuffer = vbb.asIntBuffer();
//向缓冲区中放入顶点坐标数据
vertexBuffer.put(vertices);
//设置缓冲区的起始位置
vertexBuffer.position(0);
//----------------------------------------------------
//缓存纹理数组
ByteBuffer tbb = ByteBuffer.allocateDirect(texCoords.length * 4 * 6);
tbb.order(ByteOrder.nativeOrder());
texBuffer = tbb.asIntBuffer();
//为每一个面贴上纹理
for (int i = 0; i < 6; i++) {
texBuffer.put(texCoords);
}
texBuffer.position(0);
}

模块2–自定义的渲染器SceneRenderer的实现

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
private class SceneRenderer implements Renderer {
//小星星对象
Celestial celestialSmall;
//大星星对象
Celestial celestialBig;
//连续绘制函数
public void onDrawFrame(GL10 gl) {
//清除屏幕缓存和深度缓存
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
//启用顶点坐标数据
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
//启用纹理贴图坐标数据
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
//设置当前矩阵堆栈为模型堆栈
gl.glMatrixMode(GL10.GL_MODELVIEW);
//关闭光照效果
gl.glDisable(GL10.GL_LIGHTING);
//重置当前的模型视图矩阵
gl.glLoadIdentity();
//光照属性(镜面光,前后受光3.5f)
gl.glMaterialf(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, 3.5f);
//设置顶点的位置数据
gl.glVertexPointer(3, GL10.GL_FIXED, 0, vertexBuffer);
//设置纹理点坐标数据
gl.glTexCoordPointer(2, GL10.GL_FIXED, 0, texBuffer);
//向z轴里移入6.0f
gl.glTranslatef(0.0f, 0.0f, -6.0f);
//入栈保存
gl.glPushMatrix();
// 设置3个方向的旋转
gl.glRotatef(xrot, one, 0.0f, 0.0f);
gl.glRotatef(yrot, 0.0f, one, 0.0f);
gl.glRotatef(zrot, 0.0f, 0.0f, one);
//缩放
gl.glScalef(0.75f,0.75f, 0.75f);
// 绘制正方体
for (int i = 0; i < 6; i++) {
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, i * 4, 4);
}
//关闭顶点坐标功能
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
//关闭纹理坐标功能
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
// 设置旋转角度
xrot += 0.5f;
yrot += 0.6f;
zrot += 0.3f;
//出栈恢复
gl.glPopMatrix();
//入栈保存
gl.glPushMatrix();
gl.glTranslatef(0, -24.0f, 0.0f);
celestialSmall.drawSelf(gl);
celestialBig.drawSelf(gl);
//出栈恢复
gl.glPopMatrix();
}
//视图改变函数
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置3D视窗的大小及位置
gl.glViewport(0, 0, width, height);
//将当前矩阵模式设为投影矩阵
gl.glMatrixMode(GL10.GL_PROJECTION);
//初始化单位矩阵
gl.glLoadIdentity();
//计算透视视窗的宽度、高度比
float ratio = (float) width / height;
//调用此方法设置透视视窗的空间大小
gl.glFrustumf(-ratio * 0.5f, ratio * 0.5f, -0.5f, 0.5f, 1, 100);
}
//视图创建函数
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//关闭抗抖动
gl.glDisable(GL10.GL_DITHER);
//设置特定Hint项目的模式,这里设置为使用快速模式
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
//设置屏幕背景色黑色rgb+a
gl.glClearColor(0, 0, 0, 0);
//设置着色模型为平滑着色
gl.glShadeModel(GL10.GL_SMOOTH);
//启用深度测试
gl.glEnable(GL10.GL_DEPTH_TEST);
//设置为打开背面剪裁
gl.glEnable(GL10.GL_CULL_FACE);
//创建星空
celestialSmall = new Celestial(0, 0, 3, 0, 1250);
celestialBig = new Celestial(0, 0, 5, 0,800);
//启用纹理功能
gl.glEnable(GL10.GL_TEXTURE_2D);
// 创建纹理
gl.glGenTextures(1, textureids, 0);
// 绑定要使用的纹理
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureids[0]);
// 生成纹理
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
// 线性滤波
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_LINEAR);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR);
//开启线程使星空背景移动
new Thread() {
public void run() {
while (true) {
celestialSmall.yAngle += 0.5;
if (celestialSmall.yAngle >= 360) {
celestialSmall.yAngle = 0;
}
celestialBig.yAngle += 0.5;
if (celestialBig.yAngle >= 360) {
celestialBig.yAngle = 0;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}

关于3D星空背景旋转立方体的主要逻辑代码如上所示,如果想要自己试试的话,可以去gitHub进行相关代码的下载测试,下边附上地址链接:

https://github.com/sqHayden/BlogThree3D/

好了,本次3D绘制的相关博客就写到这个地方,接下来将进入到高德地图的相关开发中。

CATALOG
  1. 1. OpenGL ES实现3D星空背景旋转立方体
    1. 1.1. OpenGL ES 简介
    2. 1.2. OpenGL ES 方法介绍
    3. 1.3. OpenGL ES 正方体绘制