20


我在 OpenGL 中平滑滚动时遇到问题(在 SGS2 和 ACE 上进行测试)
我创建了简单的应用程序 - 只有固定速度的图像水平滚动,或者只有一个图像(播放器)通过加速器移动,但它的移动并不平滑 :-(
我试过了许多不同的代码,但不满意......

首先我尝试使用GLSurfaceView.RENDERMODE_CONTINUOUSLY并将所有代码放入 onDrawFrame:

    public void onDrawFrame(GL10 gl) 
    {
      updateGame(gl, 0.017f);
      drawGame(gl); 
    }

这是最简单最流畅的!!- 但它取决于硬件速度(= 无用)


    public void onDrawFrame(GL10 gl) 
    { 
      frameTime = SystemClock.elapsedRealtime();        
      elapsedTime = (frameTime - lastTime) / 1000; 
      updateGame(gl, elapsedTime);
      drawGame(gl); 
      lastTime = frameTime; 
    }

这是最好的,但它不像以前那样流畅,有时轻弹


第二我尝试GLSurfaceView.RENDERMODE_WHEN_DIRTY,在 onDrawFrame 我只有绘图对象,并且这段代码在单独的线程中:

   while (true) 
   {
     updateGame(renderer, 0.017f);
     mGLSurfaceView.requestRender();
     next_game_tick += SKIP_TICKS;
     sleep_time = next_game_tick - System.currentTimeMillis();
     if (sleep_time >= 0) 
     {
       try 
       {
          Thread.sleep(sleep_time);
       } 
       catch (Exception e) {}
       }
       else 
       {
         Log.d("running behind: ", String.valueOf(sleep_time));
       }
     } 

这并不顺利,“落后”也没有问题


我的目标是像上面的第一个代码示例一样平滑图像移动。

可能的错误在其他地方,然后我在寻找。请问,有人可以帮我解决这个问题吗?
最好使用 RENDERMODE_WHEN_DIRTY 还是 RENDERMODE_CONTINUOUSLY?

谢谢你。

4

1 回答 1

36

几天来,我一直在与完全相同的问题作斗争。在没有任何 Android 时间参考的情况下,循环看起来很流畅,但一旦包含任何类型的“时间同步”,Android 开发控制之外的外部因素就会对最终结果造成严重的不连续性。

基本上,这些因素是:

  • eglSwapInterval 在 Android 中没有实现,所以很难知道硬件在屏幕中暴露最终绘制的时刻(硬件屏幕同步)
  • Thread.sleep 不精确。线程可能比请求的睡眠时间更多或更少。
  • SystemClock.uptimeMillis()System.nanoTime()、System.currentTimeMillis() 和其他与时间相关的测量不准确(其精确)。

该问题与绘图技术(绘图、openGL 1.0/1.1 和 2.0)和游戏循环方法(固定时间步长、插值、可变时间步长)无关。和你一样,我在尝试 Thread.sleep、疯狂的插值、计时器等。不管你会做什么,我们无法控制这些因素。

根据本网站上的许多问答,产生流畅连续动画的基本规则是:

  • 通过删除所有动态内存请求,至少减少 GC。
  • 以硬件可以处理它们的速度渲染帧(在大多数安卓设备中,40 到 60fps 是可以的)。
  • 使用带有插值的固定时间步长或可变时间步长。
  • 优化更新物理和绘制例程以在相对恒定的时间内执行而没有高峰值方差。

可以肯定的是,在发布这个问题之前,您通过优化 updateGame() 和 drawGame()(没有明显的 GC 和相对恒定的执行时间)做了很多先前的工作,以便在您的主循环中获得平滑的动画,正如您提到的:“简单而绝对流畅”。

您具有可变 stepTime 的特殊情况,并且没有特殊要求与实时事件(如音乐)完美同步,解决方案很简单:“平滑 step Time 变量”。
该解决方案适用于其他游戏循环方案(具有可变渲染的固定时间步长)并且易于移植该概念(平滑由 updateGame 产生的位移量和跨几帧的实时时钟。)

    // avoid GC in your threads. declare nonprimitive variables out of onDraw
    float smoothedDeltaRealTime_ms=17.5f; // initial value, Optionally you can save the new computed value (will change with each hardware) in Preferences to optimize the first drawing frames 
    float movAverageDeltaTime_ms=smoothedDeltaRealTime_ms; // mov Average start with default value
    long lastRealTimeMeasurement_ms; // temporal storage for last time measurement

    // smooth constant elements to play with
    static final float movAveragePeriod=40; // #frames involved in average calc (suggested values 5-100)
    static final float smoothFactor=0.1f; // adjusting ratio (suggested values 0.01-0.5)

    // sample with opengl. Works with canvas drawing: public void OnDraw(Canvas c)   
    public void onDrawFrame(GL10 gl){       
        updateGame(gl, smoothedDeltaRealTime_ms); // divide 1000 if your UpdateGame routine is waiting seconds instead mili-seconds.
        drawGame(gl);  

        // Moving average calc
        long currTimePick_ms=SystemClock.uptimeMillis();
        float realTimeElapsed_ms;
        if (lastRealTimeMeasurement_ms>0){
        realTimeElapsed_ms=(currTimePick_ms - lastRealTimeMeasurement_ms);
        } else {
                 realTimeElapsed_ms=smoothedDeltaRealTime_ms; // just the first time
        }
        movAverageDeltaTime_ms=(realTimeElapsed_ms + movAverageDeltaTime_ms*(movAveragePeriod-1))/movAveragePeriod;

         // Calc a better aproximation for smooth stepTime
        smoothedDeltaRealTime_ms=smoothedDeltaRealTime_ms +(movAverageDeltaTime_ms - smoothedDeltaRealTime_ms)* smoothFactor;

        lastRealTimeMeasurement_ms=currTimePick_ms;
    }

    // Optional: check if the smoothedDeltaRealTIme_ms is too different from original and save it in Permanent preferences for further use.

对于固定时间步长方案,可以实现中间 updateGame 来改善结果:

float totalVirtualRealTime_ms=0;
float speedAdjustments_ms=0; // to introduce a virtual Time for the animation (reduce or increase animation speed)
float totalAnimationTime_ms=0;
float fixedStepAnimation_ms=20; // 20ms for a 50FPS descriptive animation
int currVirtualAnimationFrame=0; // useful if the updateGameFixedStep routine ask for a frame number

private void updateGame(){
    totalVirtualRealTime_ms+=smoothedDeltaRealTime_ms + speedAdjustments_ms;

    while (totalVirtualRealTime_ms> totalAnimationTime_ms){
        totalAnimationTime_ms+=fixedStepAnimation_ms;
        currVirtualAnimationFrame++;
        // original updateGame with fixed step                
        updateGameFixedStep(currVirtualAnimationFrame);
    }


    float interpolationRatio=(totalAnimationTime_ms-totalVirtualRealTime_ms)/fixedStepAnimation_ms;
    Interpolation(interpolationRatio);
}

使用以下设备使用画布和 openGlES10 绘图进行测试:SG SII (57 FPS)、SG Note(57 FPS)、SG tab(60 FPS)、在 Windows XP (8 FPS) 上运行的无品牌 Android 2.3 (43 FPS) 慢速模拟器。测试平台绘制了大约 45 个对象 + 1 个巨大的背景(来自 70MP 源图像的纹理)沿着真实物理参数(km/h 和 G's)指定的路径移动,多个设备之间没有尖峰或轻弹(模拟器上的 8 FPS看起来不太好,但它按预期以恒定速度流动)

检查图表以了解 android 如何报告时间。有时 Android 报告一个很大的增量时间,只是下一个循环它比平均值小,这意味着读取 realTime 值的偏移量。

在此处输入图像描述

更详细: 在此处输入图像描述

使用 Android 的 GLSurfaceView.RENDERMODE_CONTINUOUSLY 时如何限制帧率?

System.currentTimeMillis vs System.nanoTime

System.currentTimeMillis() 方法真的返回当前时间吗?

于 2012-05-24T02:18:55.133 回答