Понимание того, как работает игровой цикл с фиксированным временным шагом, но возникают проблемы с интерполяционным рендерингом и синхронизацией

Я создал свой собственный игровой цикл на C ++ и SDL на основе этих статей:

Шаблоны игрового программирования: http://gameprogrammingpatterns.com/game-loop.html

Исправьте свой временной шаг !: https://gafferongames.com/post/fix_your_timestep/

Для образовательных предложений я хотел сделать все самостоятельно, например, рассчитать фиксированный временной шаг в зависимости от частоты обновления монитора, рассчитать время, прошедшее для каждого кадра с помощью PerformanceCounter, и выполнить синхронизацию цикла с использованием спящего режима вместо включение VSYNC.

Все обновления физики работают хорошо, и если я измеряю количество кадров в секунду, я получаю надежную частоту кадров в зависимости от значения частоты обновления и фиксированного временного шага.

Проблема в том, что я не знаю, как (и где) делать интерполяционный рендеринг. Я знаю, что мне нужно интерполировать предыдущую позицию с текущей позицией объектов с оставшимся временем, а затем визуализировать ее, но движение моих объектов все еще не плавное.

Другая проблема заключается в том, что, хотя синхронизация игрового цикла равна частоте обновления монитора, у меня все еще есть некоторые эффекты разрыва, которых я не понимаю, даже если я жду правильное количество времени между заменами буфера. Если я активирую VSYNC и прокомментирую часть синхронизации, эффект разрыва исчезает, но мой счетчик кадров не считает 60 кадров в секунду, как ожидалось, поскольку в документе SDL говорится, что с включенным VSYNC SDL_RendererPresent будет синхронизироваться с частотой обновления.

Мой код:

Main.cpp

int main()
{
  App game;

  game.Init();
  game.Run();

  return 0;
}

App.cpp

void App::Init()
{
  // SDL Init, window and render creation
  SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS);
            
  window_.CreateCustomWindow("Window", SDL_WINDOWPOS_CENTERED,
                              SDL_WINDOWPOS_CENTERED, kWindow_width,
                              kWindow_height,SDL_WINDOW_FULLSCREEN);
     
  renderer_ = SDL_CreateRenderer(window_.SDLWindow(), -1,
                                 SDL_RENDERER_ACCELERATED /*|
                                 SDL_RENDERER_PRESENTVSYNC*/);
      
  // Player creation with a position, velocity and acceleration
  player_ = new Player(Vector2{0.0f,0.0f},
                       Vector2{600.0f,0.0f},
                       Vector2{0.0f,0.0f});
      
  //Calculate the fixed time step of the update based on the monitor refresh rate
  // refresh_rate = 60Hz
  // fixed time step = 1000 / 60 = 16ms
  SDL_DisplayMode current_display;
  SDL_GetCurrentDisplayMode(0,&current_display);
  time_step_ = 1000.0 / current_display.refresh_rate;

  //Get clock frequency
  clock_frequency = SDL_GetPerformanceFrequency();
}

double App::GetTime(uint64_t start, uint64_t end)
{
  double elapsed_time = ((1000.0 * ((double)end - (double)start)) /
                         (double)clock_frequency_);
  return elapsed_time;
}
   

void App::Run()
{
  uint64_t start = SDL_GetPerformanceCounter(); 
  double lag = 0.0;
  double frame_time = 0.0;
  uint64_t end = 0; 
  while(window_.IsWindowRunning())
  {
    InputProcessing();
    
    while(lag >= time_step_)
    {
      Update((float)time_step_);
      lag -= time_step_;
    }

    window_.Clear(renderer_);    
    player_->Draw(renderer_);
    window_.SwapBuffer(renderer_);

    end = SDL_GetPerformanceCounter();
    frame_time = GetTime(start,end);

    if(time_step_ > frame_time)
    {
      SDL_Delay((uint32_t)(time_step_ - frame_time));
      frame_time = GetTime(end,SDL_GetPerformanceCounter());
      end = SDL_GetPerformanceCounter();
    }

    double frames =  (double)clock_frequency_ / ((double)end - (double)start);
    printf("MS: %f - FPS: %f \n",frame_time,frames);

    start = end;
    lag += frame_time;  
  }
  Shutdown();
}

person Mario Borrajo    schedule 16.12.2020    source источник