如何在Android上使用OpenGL渲染2D图形/图形?

时间:2011-11-18 04:20:08

标签: android opengl-es fractals

我为Android制作一个简单的分形查看应用,只是为了好玩。我之所以也使用它作为学习OpenGL的机会,因为我之前从未使用它。使用NeHe教程的Android端口作为起点,我的方法是让一个类(FractalModel)完成所有数学运算以创建分形,而FractalView则执行所有渲染。

我遇到的困难是让渲染工作。由于我基本上绘制了不同颜色的点的图形,其中每个点应该对应于1个像素,我想我通过在整个屏幕上渲染1x1矩形来处理这个问题,使用尺寸来计算偏移量。矩形与物理像素之间存在1:1的对应关系。由于每个像素的颜色将独立计算,我可以重复使用相同的渲染代码来渲染分形的不同部分(我想稍后添加平移和缩放)。

这是我写的视图类:

public class FractalView extends GLSurfaceView implements Renderer {

private float[] mVertices;
private FloatBuffer[][] mVBuffer;
private ByteBuffer[][] mBuffer;
private int mScreenWidth;
private int mScreenHeight;
private float mXOffset;
private float mYOffset;
private int mNumPixels;

//references to current vertex coordinates
private float xTL;
private float yTL;
private float xBL;
private float yBL;
private float xBR;
private float yBR;
private float xTR;
private float yTR;


public FractalView(Context context, int w, int h){
    super(context);
    setEGLContextClientVersion(1);
    mScreenWidth = w;
    mScreenHeight = h;
    mNumPixels = mScreenWidth * mScreenHeight;
    mXOffset = (float)1.0/mScreenWidth;
    mYOffset = (float)1.0/mScreenHeight;
    mVertices = new float[12];
    mVBuffer = new FloatBuffer[mScreenHeight][mScreenWidth];
    mBuffer = new ByteBuffer[mScreenHeight][mScreenWidth];
}


public void onDrawFrame(GL10 gl){
    int i,j;    
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);    
    gl.glLoadIdentity();
    mapVertices();  
    gl.glColor4f(0.0f,1.0f, 0.0f,.5f);
    for(i = 0; i < mScreenHeight; i++){
        for(j = 0; j < mScreenWidth; j++){
            gl.glFrontFace(GL10.GL_CW);
            gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVBuffer[i][j]);
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mVertices.length / 3);
            gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        }
    }




}

public void onSurfaceChanged(GL10 gl, int w, int h){
    if(h == 0) {                        //Prevent A Divide By Zero By
        h = 1;                      //Making Height Equal One
    }

    gl.glViewport(0, 0, w, h);  //Reset The Current Viewport
    gl.glMatrixMode(GL10.GL_PROJECTION);    //Select The Projection Matrix
    gl.glLoadIdentity();                    //Reset The Projection Matrix

    //Calculate The Aspect Ratio Of The Window
    GLU.gluPerspective(gl, 45.0f, (float)w / (float)h, 0.1f, 100.0f);

    gl.glMatrixMode(GL10.GL_MODELVIEW);     //Select The Modelview Matrix
    gl.glLoadIdentity();

}

public void onSurfaceCreated(GL10 gl, EGLConfig config){
    gl.glShadeModel(GL10.GL_SMOOTH);            //Enable Smooth Shading
    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);    //Black Background
    gl.glClearDepthf(1.0f);                     //Depth Buffer Setup
    gl.glEnable(GL10.GL_DEPTH_TEST);            //Enables Depth Testing
    gl.glDepthFunc(GL10.GL_LEQUAL);             //The Type Of Depth Testing To Do

    //Really Nice Perspective Calculations
    gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); 
}

private void mapVertices(){
    int i,j;
    xTL = -1;
    yTL = 1;
    xTR = -1 + mXOffset;
    yTR = 1;
    xBL = -1;
    yBL = 1 - mYOffset;
    xBR = -1 + mXOffset;
    yBR = 1 - mYOffset;
    for(i = 0; i < mScreenHeight; i++){
        for (j = 0; j < mScreenWidth; j++){
            //assign coords to vertex array         
            mVertices[0] = xBL;
            mVertices[1] = yBL;
            mVertices[2] = 0f;
            mVertices[3] = xBR;
            mVertices[4] = xBR;
            mVertices[5] = 0f;
            mVertices[6] = xTL;
            mVertices[7] = yTL;
            mVertices[8] = 0f;
            mVertices[9] = xTR;
            mVertices[10] = yTR;
            mVertices[11] = 0f;
            //add doubleBuffer
            mBuffer[i][j] = ByteBuffer.allocateDirect(mVertices.length * 4);
            mBuffer[i][j].order(ByteOrder.nativeOrder());
            mVBuffer[i][j] = mBuffer[i][j].asFloatBuffer();
            mVBuffer[i][j].put(mVertices);
            mVBuffer[i][j].position(0);
            //transform right
            transformRight();
        }
        //transform down
        transformDown();
        //reset x
        xTL = -1;
        xTR = -1 + mXOffset;
        xBL = -1;
        xBR = -1 + mXOffset;
    }



}

//transform all the coordinates 1 "pixel" to the right
private void transformRight(){
    xTL = xTL + mXOffset; //TL            
    xBL = xBL + mXOffset; //BL
    xBR = xBR + mXOffset; //BR
    xTR = xTR + mXOffset; //TR;
}

//transform all of the coordinates 1 pixel down;
private void transformDown(){
    yTL = yTL - mYOffset;                        
    yBL = yBL - mYOffset;            
    yBR = yBR - mYOffset;           
    yTR = yTR - mYOffset;
}
}

基本上我尝试的方式与this(第2课中的方格)的方式相同,但有更多的对象。我假设1和-1大致对应于屏幕边缘,(我知道这不是完全正确的,但我并不真正理解如何使用投影矩阵,并希望保持这个简单如下除非有可以从中学到的好资源,否则我可以学习,但我知道OpenGL的坐标与实际屏幕坐标是分开的。当我运行我的代码时,我只得到一个黑屏(它应该是绿色)但LogCat显示垃圾收集器正在工作,所以我知道正在发生。我不确定它是不是因为我没做正确的事,或者它真的很慢而导致的错误。在任何一种情况下,我应该做些什么?我觉得我可能会犯这个错误。我环顾四周,大部分教程和示例均基于上面的链接。

编辑:我知道我可以通过生成一个填满整个屏幕并只绘制它的纹理来解决这个问题,尽管我读过的链接说它会慢一些,因为你不应该重绘每一帧纹理。也就是说,我只需要在透视图发生变化时重新绘制纹理,因此我可以编写代码来考虑这一点。我目前遇到的主要困难是绘制位图,并使其正确显示。

1 个答案:

答案 0 :(得分:1)

我认为空白屏幕是由于您多次交换缓冲区,以及每帧都生成所有顶点缓冲区的事实。在一个帧中成千上万的缓冲交换和数千个缓冲区创建将变得非常缓慢。

有一点需要提及的是,Android设备的内存有限,因此垃圾收集器的工作可能表明您的缓冲区创建代码占用了大量可用内存,并且设备正试图释放一些内存。创建新缓冲区。

我建议创建一个纹理,用每个帧填充像素数据,然后渲染到填充屏幕的单个方块。这将大大提高您的速度,并使您的程序更加灵活。

修改 请查看此处的教程:http://www.nullterminator.net/gltexture.html以了解如何创建纹理并加载它们。您基本上需要使用自己的数据填充BYTE *数据。

如果要动态更改数据,则需要更新纹理数据。请在此处使用以下信息:http://www.opengl.org/wiki/Texture:在有关纹理图像修改的部分中。