Android OpenGLES中的恒定帧率

3

大家好,我正在使用OpenGLES 1.0在Eclipse上为Android开发一个简单的游戏。我使用三星Galaxy S2 Android(2.3)作为开发设备。

我有一个关于双核处理器和保持帧率恒定的问题。

所以我已经成功创建了GLSurfaceView并覆盖了onDrawFrame()函数,在这里我调用了LogicUpdate(deltatime)函数和Render()函数。

是的,现在所有的操作都在单线程中进行。

我的问题是关于双核处理器。如果我通过进入设置->省电模式并勾选系统省电来禁用双核处理器,我会发现渲染自动锁定在30 FPS。但是,如果我取消勾选系统省电来启用双核处理器,我会发现渲染被锁定在60 FPS,但手机会变得很热并且电池会非常快地耗尽。

所以我的想法是将游戏运行在30 FPS以节省一些电池电量。

为了实现这一点,我使用以下代码。

在执行逻辑更新之前,我调用了这段代码,记住所有这些都是在onDrawFrame()函数中完成的。

if( CONST_FPS > 0 && StartTime > 0 )
{           
    /////////////////////////////////////////////////////////////////
    // Get frame time
    ////////////////////////////////////////////////////////////////
    long endTime  = System.currentTimeMillis(); 
    long time     = endTime - StartTime;
    //              
    long wantedtime = 1000 / CONST_FPS;
    //
    long wait = 0;
    if( time < wantedtime )
    {
        wait = wantedtime - time;
        //                  
        Thread.sleep(wait);
    }
    else
    {
        //Time to big game will slow down
    }                           
}

常量 CONST_FPS 的值为 30。

然后

StartTime = System.currentTimeMillis(); //

UpdateLogic(1.0 / CONST_FPS);
Render();

游戏在30FPS下的玩法非常流畅,主要是因为它不需要锁定FPS。但是,当试图将60FPS锁定到30FPS时,会出现卡顿。我进行了一些研究,并发现Thread.Sleep()不精确。这是真的吗?还有什么其他方法可以使游戏在将60FPS锁定到30FPS时更加流畅。

感谢您的回答...

2个回答

6

您应该使用经过的时间来调整所有的运动,这样在不同的FPS速率下,运动仍然保持平滑。您可以像这样获取经过的时间:

long currentTime = System.currentTimeMillis();
float elapsed = (System.currentTimeMillis() - lastFrameTime) * .001f;//convert ms to seconds
lastFrameTime = currentTime; 

然后以每秒的单位表达速度,并像这样更新位置:
sprite.x += (sprite.xspeed * elapsed);

3
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1);
    GLES20.glEnable(GLES20.GL_DEPTH_TEST);
    GLES20.glDepthFunc(GLES20.GL_LEQUAL);
    GLES20Renderer.programLight = GLES20.glCreateProgram();
    int vertexShaderLight       = GLES20Renderer.loadShader(GLES20.GL_VERTEX_SHADER, GLES20Renderer.vertexShaderCodeLight);
    int fragmentShaderLight     = GLES20Renderer.loadShader(GLES20.GL_FRAGMENT_SHADER, GLES20Renderer.fragmentShaderCodeLight);
    GLES20.glAttachShader(GLES20Renderer.programLight, vertexShaderLight);
    GLES20.glAttachShader(GLES20Renderer.programLight, fragmentShaderLight);
    GLES20.glLinkProgram(GLES20Renderer.programLight);
    System.gc();
}

public void onSurfaceChanged(GL10 gl, int width, int height) {
    gl.glViewport(0, 0, width, height);
    float ratio = (float) width / height;
    Matrix.setLookAtM(GLES20Renderer.ViewMatrix, 0, 0, 0, 5f, 0, 0, 0, 0, 1, 0);
    Matrix.frustumM(GLES20Renderer.ProjectionMatrix, 0, -ratio, ratio, -1, 1, 2, 8);
    Matrix.setIdentityM(LightModelMatrix, 0);
    Matrix.translateM(LightModelMatrix, 0, -1.0f, 0.0f, 3f);
    Matrix.multiplyMV(LightPosInWorldSpace, 0, LightModelMatrix, 0, LightPosInModelSpace, 0);
    Matrix.multiplyMV(LightPosInEyeSpace, 0, ViewMatrix, 0, LightPosInWorldSpace, 0);

    System.gc();
}

public void onDrawFrame(GL10 gl) {
    long deltaTime,startTime,endTime;
    startTime   = SystemClock.uptimeMillis() % 1000;
    gl.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    synchronized (this) {
        updateModel(GLES20Renderer._xAngle, GLES20Renderer._yAngle, GLES20Renderer._zAngle);            
    }
    renderModel(gl);
    endTime     = SystemClock.uptimeMillis() % 1000;
    deltaTime   = endTime - startTime;
    if (deltaTime < 30) {
        try {
            Thread.sleep(30 - deltaTime);
        } catch (InterruptedException e) {
            //e.printStackTrace();
        }
    }

    System.gc();
}

注意:

  1. 不要在UI线程中使用sleep,这样非常非常非常糟糕。
  2. 使用静态方法:尽管这是不好的面向对象编程实践,但在这里很有用。
  3. 使用顶点缓冲对象。
  4. 在onsurfacechanged中初始化并获取位置(glget_location)。
  5. 并不总是因为Froyo垃圾回收器差,而是在Gingerbread上触摸驱动程序会触发太多事件(可以在Google上搜索),因此逐渐切换到其他不使用触摸的应用/游戏输入方式,例如Android传感器。
  6. 升级到Gingerbread操作系统。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接