Как я могу обеспечить правильную скорость анимации в Qt, используя OpenGL? - PullRequest
1 голос
/ 26 января 2011

У меня есть 200 кадров для отображения в секунду.Рамки очень простые, черно-белые, всего пара строк.Таймер управляет анимацией.Цель состоит в том, чтобы воспроизвести кадры в прибл.200 кадров в секунду.

В Linux я установил таймер на 5 мс и позволил ему отображать каждый кадр (то есть 200 кадров в секунду).Работает просто отлично, но не работает под Win 7.

Под Win 7 (на той же машине) мне пришлось установить таймер на 20 мс и позволить ему отображать каждые 4 кадра (50 кадров в секунду × 4 = 200).Я нашел эти магические числа методом проб и ошибок.

Что я должен сделать, чтобы гарантировать (в разумных пределах), что анимация будет воспроизводиться на правильной скорости на компьютере пользователя?

ДляНапример, что если компьютер пользователя может делать только 30 кадров в секунду или 60 кадров в секунду?

Ответы [ 4 ]

4 голосов
/ 26 января 2011

Короткий ответ: вы не можете (в общем).

Для лучшей эстетики большинство оконных систем по умолчанию включает "vsync", что означает, что перерисовки экрана происходят с частотой обновления монитора. В старые времена ЭЛТ вы могли бы получить 75-90 Гц с высококлассным монитором, но с сегодняшними ЖК-дисплеями вы, вероятно, застряли на скорости 60 кадров в секунду.

Тем не менее, существуют расширения OpenGL, которые могут программно отключать VSync (не запоминать имя расширения), и вы часто можете отключить его на уровне драйвера. Тем не менее, независимо от того, что вы делаете (исключая пользовательское оборудование), вы не сможете отображать полные кадры со скоростью 200 кадров в секунду.

Теперь неясно, есть ли у вас предварительно отрендеренные изображения, которые нужно отображать со скоростью 200 кадров в секунду, или вы рендерите с нуля и надеетесь достичь 200 кадров в секунду. Если это первое, хорошим вариантом может быть использование таймера, чтобы определить, какой кадр следует отображать (при каждом обновлении 60 Гц), и использовать это значение для линейной интерполяции между двумя предварительно отрендеренными кадрами. Если это последнее, я бы просто использовал таймер для управления движением (или тем, что динамично в вашей сцене) и рендерингом соответствующей сцены с учетом времени. Более быстрое аппаратное обеспечение или отключенный VSYNC даст вам больше кадров (следовательно, более плавную анимацию, по модулю разрыва) за то же время и т. Д. Но сцена в любом случае развернется в нужном темпе.

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

2 голосов
/ 26 января 2011

Я уже читал, что у вас есть данные, сэмплированные с частотой 200 Гц, которые вы хотите воспроизводить с естественной скоростью. То есть одна секунда выборочных данных должна быть обработана за одну секунду.

Во-первых: забудьте об использовании таймеров для координации рендеринга, это вряд ли будет работать должным образом. Вместо этого вы должны измерить время, которое занимает полный цикл рендеринга (включая v-sync) и увеличить счетчик времени анимации этим. Теперь 200 Гц - это уже какое-то очень хорошее временное разрешение, поэтому, если данные достаточно гладкие, тогда вообще не нужно интерполировать. Так что-то вроде этого (псевдокод):

objects[] # the objects, animated by the animation
animation[] # steps of the animation, sampled at 200Hz
ANIMATION_RATE = 1./200. # Of course this shouldn't be hardcoded,
                         # but loaded with the animation data

animationStep = 0

timeLastFrame = None
drawGL():
    timeNow = now() # time in seconds with (at least) ms-accuracy
    if timeLastFrame:
        stepTime = timeNow - timeLastFrame
    else:
        stepTime = 0

    animationStep = round(animationStep + stepTime * ANIMATION_RATE)
    drawObjects(objects, animation[animationStep])

    timeLastFrame = timeNow

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

drawGL():
    timeNow = now() # time in seconds with (at least) ms-accuracy
    if timeLastFrame:
        stepTime = timeNow - timeLastFrame
    else:
        stepTime = 0

    timeRenderStart = now()
    animationStep = round(animationStep + stepTime * ANIMATION_RATE)
    drawObjects(objects, animation[animationStep])

    glFinish() # don't call SwapBuffers
    timeRender = now() - timeRenderStart

    setup_GL_for_motion_blur()

    intermediates = floor(stepTime / timeRender) - 1 # subtract one to get some margin
    backstep = ANIMATION_RATE * (stepTime / intermediates)
    if intermediates > 0:
        for i in 0 to intermediates:
            drawObjects(objects, animation[animationStep - i * backstep])

    timeLastFrame = timeNow
0 голосов
/ 26 января 2011

Здесь, вероятно, следует учитывать два совершенно независимых фактора:

  1. Насколько быстро работает пользовательский компьютер?Возможно, вы не достигли целевой частоты кадров из-за того, что машина все еще обрабатывает последний кадр к тому времени, когда она готова начать рисовать следующий кадр.

  2. Какое разрешение таймеров вы используете?У меня сложилось впечатление (хотя у меня нет доказательств, подтверждающих это), что таймеры в операционных системах Windows обеспечивают гораздо более низкое разрешение, чем в Linux.Таким образом, вы можете запросить сон (например) 5 мс и вместо этого получить сон в 15 мс.

Дальнейшее тестирование должно помочь вам выяснить, какой из этих двух сценариев болееотношение к вашей ситуации.

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

Если проблема заключается в разрешении таймера, вы можете посмотреть на альтернативные API таймера (Windows API предоставляет две разные функции таймера, каждый с разным разрешением, возможно, вы используете неправильное), или попытаться компенсировать, задавдля меньших временных интервалов (как в предложении Kdoto).Однако выполнение этого может на самом деле ухудшить производительность, поскольку теперь вы выполняете намного больше обработки, чем раньше - вы можете заметить скачок использования вашего ЦП при этом методе.

Редактировать:

Как упоминает Дрю Холл в своем ответе, есть еще один целый сайт: частота обновления, которую вы получаете в коде, может сильно отличаться от фактической частоты обновления, отображаемой на экране.Однако это зависит от устройства вывода, и из вашего вопроса звучит, как будто проблема в коде, а не в оборудовании вывода.

0 голосов
/ 26 января 2011

Один из способов - спать 1 мс на каждой итерации цикла и проверять, сколько времени прошло.

Если прошло больше целевого времени (для 200 кадров в секунду, то есть 1000/200 = 5 мс), затем нарисуйте рамку.В противном случае перейдите к следующей итерации цикла.

Например, некоторый псевдокод:

target_time = 1000/200; // 200fps => 5ms target time.
timer = new timer(); // Define a timer by whatever method is permitted in your
                     // implementation.
while(){
    if(timer.elapsed_time < target_time){
       sleep(1);
       continue;
    }

    timer.reset(); // Reset your timer to begin counting again.

    do_your_draw_operations_here(); // Do some drawing.

}

Преимущество этого метода в том, что, если компьютер пользователя не поддерживает скорость 200 кадр / с,продолжайте рисовать как можно быстрее, и сон никогда не будет вызван.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...