OpenGL(全写Open Graphics Library)是一个跨语言、跨平台的三维图象编程接口,同样他也可以用来创建二维图像。OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计。android 平台上同样集成了opengl es的开发包,opengl es在android平台上的运用,既有利于充分利用android平台不断进步的硬件配置,也能为用户提供更加多姿多彩的视觉体验。
android平台创建三维动画有二种方式,一种是使用matrix三维变形实现伪3D,一种就是使用opengl创建真3D。使用matrix三维变形,方法简单,速度快,效率高,但因为其是伪3D,所以抛开视觉感受不谈,其缺陷也非常明显,他没有真正的3D建模,只是对二维的VIEW做3D移动,大小变换和旋转等操作,当你需要创建一个旋转的六面体时,你需要创建六个VIEW,并让他们实现绕一个虚拟的轴做同步旋转,当这个3D模型有足够多的面时,其复杂度大大增加了,因为在初始化时需要提前计算每个面的初始位置信息,旋转,大小,位移的同步信息,这对于非矩形的view来说,这种计算相当繁琐而且易于出错并且不容易精确,而且目前来看,使用伪3D技术构建规则曲面似乎是难以实现。这时opengl es的优势相当明显。你可以创建任意形状的多面体,只要把VIEW转为BITMAP为多面体贴图就行了。
android使用opengl ES创建立方体,需要用到下面一些函数。
gl.glFrontFace(GL10.GL_CW);
gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
void glFrontFace(GLenum mode);
作用是控制多面形的正面是如何决定的。在默认情况下,mode是GL_CCW。一个多边形有两个面,每个面使用不同的介质贴图,旋转时是可以看到
mode的值为:
GL_CCW 表示窗口坐标上投影多边形的顶点顺序为逆时针方向的表面为正面。
GL_CW 表示顶点顺序为顺时针方向的表面为正面。
void glVertexPointer(GLint size,
GLenum type,
GLsizei stride,
const GLvoid * pointer)
作用是指定多面体每个顶点的坐标, size:指定了每个顶点对应的坐标个数,只能是2,3,4中的一个,默认值是4
type:指定了数组中每个顶点坐标的数据类型,可取常量:GL_BYTE, GL_SHORT,GL_FIXED,GL_FLOAT;
stride:指定了连续顶点间的字节排列方式,如果为0,数组中的顶点就会被认为是按照紧凑方式排列的,默认值为0
pointer:制订了数组中第一个顶点的首地址,默认值为0,对于我们的android,大家可以不用去管什么地址的,一般给一个IntBuffer就可以了。
需要说明的是第二个参数,一般在android中,会使用 GL10.GL_FIXED和GL10. FLOAT, 整型和浮点型相互对应, 0x10000=1.0f,整型的低八位表示浮点的小数,高八位表示浮点小数部分。而且第四个参数必须是 nativeOrder,如果是直接赋值的数组,需要使用下面代码转化
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asIntBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
gl.glColorPointer
这个是颜色了,和上一个函数一样,是指定每个顶点的颜色值,参数结构也类似
gl.glDrawElements
这个是用来显示的,六面体六个面,有12个三角形,每个三角形三个顶点,共36个
这样就可以得到下面的四面体的类:
package com.example.openglactivity;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
public class Cube {
private IntBuffer mVertexBuffer;
private IntBuffer mColorBuffer;
private ByteBuffer mIndexBuffer;
// 定义本程序所使用的纹理
private int texture;
public Cube()
{
int one = 0x10000; //int 10000=1.0f
int vertices[] = {
-one, -one, -2000,//-one, //第三象限
one, -one, -2000,//-one, //第四象限
one, one, -2000,//-one, //第一象限
-one, one, -2000,//-one, //第二象限
-one, -one, 0, //one, //第三象限
one, -one, 0, //one, //第四象限
one, one, 0, //one, //第一象限
-one, one, 0, //one, //第二象限
};
int colors[] = {
0, 0, 0, one,
one, 0, 0, one,
one, one, 0, one,
0, one, 0, one,
0, 0, one, one,
one, 0, one, one,
one, one, one, one,
0, one, one, one,
};
byte indices[] = {
0, 4, 5, 0, 5, 1,
1, 5, 6, 1, 6, 2,
2, 6, 7, 2, 7, 3,
3, 7, 4, 3, 4, 0,
4, 7, 6, 4, 6, 5,
3, 0, 1, 3, 1, 2
};
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asIntBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);
cbb.order(ByteOrder.nativeOrder());
mColorBuffer = cbb.asIntBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);
mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
mIndexBuffer.put(indices);
mIndexBuffer.position(0);
}
public void draw(GL10 gl)
{
gl.glFrontFace(GL10.GL_CW);
gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);
gl.glColorPointer(4, GL10.GL_FIXED, 0, mColorBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE, mIndexBuffer);
}
}
这是使用opengl创建的体面体,要导入android中使用,还需要经过一些步骤,一个是GLSurfaceView,一个是Renderer,Renderer是GLSurfaceView提供的接口,我们需要通过重载Renderer加载我们的四面体,然后通过setRenderer把包含我们六面体Renderer传给GLSurfaceView,最后通过activity的setContentView把GLSurfaceView设置为activity的内容,代码如下:
myGLRenderer 派生自Renderer,需要实现 Renderer的 onSurfaceCreated, onSurfaceChanged, onDrawFrame三个方法。这是比较重要的,定义了显示3D模型的环境,显示方式显示特效等一系列东西。 onSurfaceCreated和 onSurfaceChanged熟悉surfaceview的人肯定不陌生,他原本就是surfaceview的方法,我们对于3D屏幕初始化,opengl环境设置都会在这两个函数里实现,然后就是onDrawFrame,用来显示,大致来说,应该和view的onDraw差不多的功能,能够自动适时刷新。用到的函数是这几个:
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
设置背景颜色
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
启动顶点数组支持,类似的函数还有
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
一个启动法向量支持,一个启动纹理渲染支持
gl.glViewport(0, 0, width, height);
设置显示区域,一般就是整个屏幕吧
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
这两句是设置当前矩阵模式和重载矩阵,不可省略,有三种模式,GL_MODELVIEW,对模型视景矩阵堆栈应用随后的矩阵操作.GL_PROJECTION,对投影矩阵应用随后的矩阵操作.GL_TEXTURE,对纹理矩阵堆栈应用随后的矩阵操作.设置矩阵后必须使用 glLoadIdentity才会生效,该函数的功能是重置当前指定的矩阵为设置矩阵。还有一个函数gluPerspective也很重要,他是创建一个投影矩阵并且与当前矩阵相乘,得到的矩阵设定为当前变换,但要先通过glMatrixMode设定成GL_PROJECTION 投影矩阵才会得到想要的投影矩阵变换。
gl.glFrustumf(-ratio, ratio, -1, 1, 3, 100);
ratio=(float) width / height,所以 glFrustumf是设置长宽比例,因为opengl是按默认的正方形屏幕投影的,这样当在一个高比宽大的长方形的屏上时,投影的正方形会变成高比宽大的长方形,要真实的投影,需要通过这个函数来校正投投影。
GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
创建观察者坐标系,这里可以参考下面的文章:http://www.cnblogs.com/chengmin/archive/2011/09/12/2174004.html
gl.glRotatef(ang, 0f, 0.2f, 0f);
这个是对函数进行旋转,第一个参数是角度,后面三个参数是XYZ三个方向,类似的函数还有gl.glTranslatef(0, 0, -3.5f);移动操作
package com.example.openglactivity;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLU;
public class myGLRenderer implements Renderer {
private Cube m_cube;
private float ang = 0.0f;
public myGLRenderer()
{
m_cube = new Cube();
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO Auto-generated method stub
gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); //设置清除色
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
gl.glViewport(0, 0, width, height);//设置视口
float ratio = (float) width / height;
gl.glMatrixMode(GL10.GL_PROJECTION); // 设置当前矩阵为投影矩阵
gl.glLoadIdentity(); // 重置矩阵为初始值
gl.glFrustumf(-ratio, ratio, -1, 1, 3, 100); // 根据长宽比设置投影矩阵
}
@Override
public void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);//清空缓存
// 设置当前矩阵为模型视图模式 //
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity(); // reset the matrix to its default state
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
// 设置视点
GLU.gluLookAt(gl, 0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
gl.glRotatef(ang, 0f, 0.2f, 0f);
m_cube.draw(gl);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
ang+=1.0f;
}
}
myGLSurfaceView类派生自 GLSurfaceView,GLSurfaceView 派生自SurfaceView,这下一切就熟悉了,SurfaceView是为了动画,特效而设计的高效UI类。 GLSurfaceView是专门为opengl服务的类。
这里也十分简单。 setRenderer就行了,可以查看setRenderer在 GLSurfaceView里的实现:
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory();
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(renderer);
mGLThread.start();
}
开了个新线程去操作 renderer。下面是 myGLSurfaceView代码
package com.example.openglactivity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.view.KeyEvent;
public class myGLSurfaceView extends GLSurfaceView {
private myGLRenderer mrender;
public myGLSurfaceView(Context context) {
super(context);
// TODO Auto-generated constructor stub
//getHolder().setFormat(PixelFormat.TRANSLUCENT);
mrender = new myGLRenderer();
setRenderer(mrender);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
return super.onKeyDown(keyCode, event);
}
}
最后看一下 Activity的调用,一切都了解了,和调用普通的view没有区别。
package com.example.openglactivity;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
private myGLSurfaceView mGLSurfaceView;
public static Activity instance = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
instance = this;
mGLSurfaceView = new myGLSurfaceView(this);
setContentView(mGLSurfaceView);//这里我们用mGLSurfaceView来替换以前常用的R.layout.main
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
instance = null;
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
使用这个框架,我们可以创建各种3D模型用来显示,三角形,四边形,四面体,棱柱,棱锥。并使用他们实现旋转,移动等操作的演示。要想使用他来构建3D用户界面,还是有不少的路要走,一是渲染,让其变得更漂亮,二是交互,让其能根据用户操作做出反映。日后待续
参考:
http://www.cnblogs.com/chengmin/archive/2011/09/12/2174004.html
http://www.linuxidc.com/Linux/2011-10/45106p4.htm