Android opengl es创建动画详解

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

你可能感兴趣的:(android应用开发,MTK专栏)