Постоянная скорость игры не зависит от переменной FPS в OpenGL с GLUT? - PullRequest
8 голосов
/ 18 марта 2011

Я читал Koen Witters подробную статью о различных решениях игрового цикла, но у меня возникли некоторые проблемы при реализации последнего с GLUT, который является рекомендуемым.

Послечитая пару статей, учебных пособий и кода от других людей о том, как добиться постоянной скорости игры, я думаю, что то, что я в настоящее время реализовал (я опубликую код ниже), это то, что Коен Виттерс назвал Скорость игры зависит отПеременная FPS , вторая в его статье.

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

GLUT Toolkit:

  • GLUT - это набор инструментов OpenGL, который помогает выполнять общие задачив OpenGL.
  • glutDisplayFunc(renderScene) получает указатель на функцию обратного вызова renderScene(), которая будет отвечать за отображение.Функция renderScene() будет вызываться только один раз после регистрации обратного вызова.
  • glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0) принимает количество миллисекунд, которое должно пройти перед вызовом обратного вызова processAnimationTimer().Последний аргумент - это просто значение для передачи в обратный вызов таймера.processAnimationTimer() будет вызываться не каждый TIMER_MILLISECONDS, а только один раз.
  • Функция glutPostRedisplay() запрашивает GLUT для рендеринга нового кадра, поэтому мы должны вызывать его каждый раз, когда мы что-то меняем в сцене.
  • glutIdleFunc(renderScene) можно использовать для регистрации обратного вызова на renderScene() (это не делает glutDisplayFunc() неактуальным), но этой функции следует избегать, поскольку простой обратный вызов непрерывно вызывается, когда события не принимаются, увеличиваязагрузка процессора.
  • Функция glutGet(GLUT_ELAPSED_TIME) возвращает количество миллисекунд с момента вызова glutInit (или первого вызова glutGet(GLUT_ELAPSED_TIME)).Это таймер у нас с GLUT.Я знаю, что есть лучшие альтернативы для таймеров с высоким разрешением, но давайте пока остановимся на этом.

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

Текущая реализация:

Теперь я не уверен, что правильно выполнил второе решениеПредложенный Коеном Скорость игры зависит от переменной FPS .Соответствующий код выглядит следующим образом:

#define TICKS_PER_SECOND 30
#define MOVEMENT_SPEED 2.0f

const int TIMER_MILLISECONDS = 1000 / TICKS_PER_SECOND;

int previousTime;
int currentTime;
int elapsedTime;

void renderScene(void) {
    (...)

    // Setup the camera position and looking point
    SceneCamera.LookAt();

    // Do all drawing below...

    (...)
}

void processAnimationTimer(int value) {
    // setups the timer to be called again
    glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0);

    // Get the time when the previous frame was rendered
    previousTime = currentTime;

    // Get the current time (in milliseconds) and calculate the elapsed time
    currentTime = glutGet(GLUT_ELAPSED_TIME);
    elapsedTime = currentTime - previousTime;

    /* Multiply the camera direction vector by constant speed then by the
       elapsed time (in seconds) and then move the camera */
    SceneCamera.Move(cameraDirection * MOVEMENT_SPEED * (elapsedTime / 1000.0f));

    // Requests to render a new frame (this will call my renderScene() once)
    glutPostRedisplay();
}

void main(int argc, char **argv) {
    glutInit(&argc, argv);

    (...)

    glutDisplayFunc(renderScene);

    (...)

    // Setup the timer to be called one first time
    glutTimerFunc(TIMER_MILLISECONDS, processAnimationTimer, 0);
    // Read the current time since glutInit was called
    currentTime = glutGet(GLUT_ELAPSED_TIME);

    glutMainLoop();
}

Эта реализация не подходит.Это работает в том смысле, что скорость игры постоянно зависит от FPS.Таким образом, перемещение из точки A в точку B занимает одно и то же время, независимо от высокой / низкой частоты кадров.Тем не менее, я считаю, что я ограничиваю частоту кадров при таком подходе.[ РЕДАКТИРОВАТЬ: Каждый кадр будет отображаться только при вызове обратного вызова времени, это означает, что частота кадров будет примерно около TICKS_PER_SECOND кадров в секунду.Это не правильно, вы не должны ограничивать свое мощное оборудование, это неправильно.Однако я понимаю, что мне все еще нужно вычислить elapsedTime.Просто потому, что я говорю GLUT, чтобы он вызывал таймер обратного вызова каждые TIMER_MILLISECONDS, это не значит, что он всегда будет делать это вовремя.]

Я не уверен, как я могу это исправить и бытьсовершенно честно, я понятия не имею, что такое игровой цикл в GLUT, вы знаете, цикл while( game_is_running ) в статье Коэна.[ EDIT: Насколько я понимаю, GLUT управляется событиями , и этот игровой цикл начинается, когда я звоню glutMainLoop() (который никогда не возвращается), да?]

Я думал, что смогу зарегистрировать неактивный обратный вызов с glutIdleFunc() и использовать его вместо glutTimerFunc(), только рендеринг при необходимости (вместо обычного времени), но когда я проверял это с пустым обратным вызовом (как void gameLoop() {})и он в основном ничего не делал, только черный экран, процессор ускорился до 25% и оставался там до тех пор, пока я не убил игру, и она не пришла в норму.Поэтому я не думаю, что это путь для подражания.

Использование glutTimerFunc() определенно не является хорошим подходом для выполнения всех движений / анимаций, основанных на этом, поскольку я ограничиваю свою игру постоянным FPS, а не круто.Или, может быть, я использую это неправильно, и моя реализация не так?

Как именно я могу иметь постоянную скорость игры с переменной FPS?Точнее, как правильно реализовать решение Koen Constant Game Speed ​​с максимальным FPS (четвертое в его статье) с GLUT?Может быть, это невозможно вообще с GLUT?Если нет, каковы мои альтернативы? Каков наилучший подход к этой проблеме (постоянная скорость игры) с GLUT?

[EDIT] Другой подход:

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

Мой код теперь превратился в это:

int previousTime;
int currentTime;
int elapsedTime;

void renderScene(void) {
    (...)

    // Setup the camera position and looking point
    SceneCamera.LookAt();

    // Do all drawing below...

    (...)
}

void renderScene(void) {
    (...)

    // Get the time when the previous frame was rendered
    previousTime = currentTime;

    // Get the current time (in milliseconds) and calculate the elapsed time
    currentTime = glutGet(GLUT_ELAPSED_TIME);
    elapsedTime = currentTime - previousTime;

    /* Multiply the camera direction vector by constant speed then by the
       elapsed time (in seconds) and then move the camera */
    SceneCamera.Move(cameraDirection * MOVEMENT_SPEED * (elapsedTime / 1000.0f));

    // Setup the camera position and looking point
    SceneCamera.LookAt();

    // All drawing code goes inside this function
    drawCompleteScene();

    glutSwapBuffers();

    /* Redraw the frame ONLY if the user is moving the camera
       (similar code will be needed to redraw the frame for other events) */
    if(!IsTupleEmpty(cameraDirection)) {
        glutPostRedisplay();
    }
}

void main(int argc, char **argv) {
    glutInit(&argc, argv);

    (...)

    glutDisplayFunc(renderScene);

    (...)

    currentTime = glutGet(GLUT_ELAPSED_TIME);

    glutMainLoop();
}

Вывод, он работает, или так кажется.Если я не перемещаю камеру, загрузка процессора низкая, ничего не отображается (для целей тестирования у меня есть только сетка, растягивающаяся на 4000.0f, в то время как zFar установлен на 1000.0f).Когда я начинаю двигать камеру, сцена начинает перерисовываться.Если я продолжу нажимать клавиши перемещения, загрузка процессора увеличится;это нормальное поведение.Когда я перестаю двигаться, он падает обратно.

Если я что-то не упустил, пока это кажется хорошим подходом.Я нашел эту интересную статью на iDevGames, и на эту реализацию, вероятно, влияет проблема, описанная в этой статье.Что вы думаете об этом?

Обратите внимание, что я просто делаю это для развлечения, у меня нет намерений создавать какую-то игру для распространения или что-то в этом роде, по крайней мере, в ближайшем будущем.Если бы я это сделал, я бы, вероятно, пошел с чем-то еще, кроме GLUT.Но так как я использую GLUT, а не проблему, описанную в iDevGames, вы считаете, что этой последней реализации достаточно для GLUT?Единственная реальная проблема, о которой я могу думать сейчас, это то, что мне нужно будет продолжать звонить glutPostRedisplay() каждый раз, когда сцена меняет что-то, и продолжать вызывать это, пока не будет ничего нового для перерисовки.Я думаю, что в код добавлена ​​небольшая сложность для лучшего дела.

Что вы думаете?

Ответы [ 3 ]

1 голос
/ 18 марта 2011

перенасыщение рассчитано на быть игровым циклом. Когда вы вызываете glutMainLoop (), он выполняет цикл for без условия завершения, кроме сигнала exit (). Вы можете реализовать свою программу так, как вы делаете сейчас, но вам нужно внести небольшие изменения. Во-первых, если вы хотите узнать, что такое FPS, вы должны поместить это отслеживание в функцию renderScene (), а не в функцию обновления. Естественно, ваша функция обновления вызывается так быстро, как указано таймером, и вы рассматриваете elapsedTime как меру времени между кадрами. В общем, это будет верно, потому что вы вызываете glutPostRedisplay довольно медленно, и glut не будет пытаться обновить экран, если в этом нет необходимости (нет необходимости перерисовывать, если сцена не изменилась). Однако, есть другие случаи, когда renderScene будет вызываться. Например, если вы перетащите что-нибудь через окно. Если вы это сделаете, вы увидите более высокий FPS (если вы правильно отслеживали FPS в функции рендеринга).

0 голосов
/ 14 ноября 2014

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

Отпустите кнопку мыши при перетаскивании, и вы сможете «выбросить» объект, преобразованный этой матрицей.

Если fxFPS равно true, то частота кадров рендеринга регулируется до частоты кадров анимации;в противном случае идентичные кадры отрисовываются повторно для бенчмаркинга, даже если пройдет не достаточно миллисекунд для запуска какой-либо анимации.

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

Чтобы ускорить анимацию, вращения выполняются многократно в цикле.Такая петля не слишком медленная по сравнению с возможностью выполнения триггера с адаптивным углом поворота;просто будьте осторожны с тем, что вы помещаете в любой цикл, который на самом деле выполняется дольше, чем ниже FPS.Этот цикл занимает гораздо меньше, чем дополнительный кадр, для каждого пропуска кадров, который он учитывает, поэтому он достаточно безопасен.

int xSt, ySt, xCr, yCr, msM = 0, msOld = 0;
bool dragging = false, spin = false, moving = false;
glm::mat4 mouseRot(1.0f), continRot(1.0f);
float twoOvHght; // Set in reshape()
glm::mat4 mouseRotate(bool slow) {
    glm::vec3 axis(twoOvHght * (yCr - ySt), twoOvHght * (xCr - xSt), 0); // Perpendicular to mouse motion
    float len = glm::length(axis);
    if (slow) { // Slow rotation; divide angle by mouse-delay in milliseconds; it is multiplied by frame delay to speed it up later
        int msP = msM - msOld;
        len /= (msP != 0 ? msP : 1);
    }
    if (len != 0) axis = glm::normalize(axis); else axis = glm::vec3(0.0f, 0.0f, 1.0f);
    return rotate(axis, cosf(len), sinf(len));
}
void mouseMotion(int x, int y) {
    moving = (xCr != x) | (yCr != y);
    if (dragging & moving) {
        xSt = xCr; xCr = x; ySt = yCr; yCr = y; msOld = msM; msM = glutGet(GLUT_ELAPSED_TIME);
        mouseRot = mouseRotate(false) * mouseRot;
    }
}
void mouseButton(int button, int state, int x, int y) {
    if (button == 0) {
        if (state == 0) {
            dragging = true; moving = false; spin = false;
            xCr = x; yCr = y; msM = glutGet(GLUT_ELAPSED_TIME);
            glutPostRedisplay();
        } else {
            dragging = false; spin = moving;
            if (spin) continRot = mouseRotate(true);
        }
    }
}

А потом ...

bool fxFPS = false;
int T = 0, ms = 0;
const int fDel = 20;
void display() {
    ms = glutGet(GLUT_ELAPSED_TIME);
    if (T <= ms) { T = ms + fDel;
        for (int lp = 0; lp < fDel; lp++) {
            orient = rotY * orient; orientCu = rotX * rotY * orientCu; // Auto-rotate two orientation quaternions
            if (spin) mouseRot = continRot * mouseRot; // Track rotation from thowing action by mouse
        }
        orient1 = glm::mat4_cast(orient); orient2 = glm::mat4_cast(orientCu);
    }
    // Top secret animation code that will make me rich goes here
    glutSwapBuffers();
    if (spin | dragging) { if (fxFPS) while (glutGet(GLUT_ELAPSED_TIME) < T); glutPostRedisplay(); } // Fast, repeated updates of the screen
}

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

Синхронизация многопользовательских игр - это еще 18 разговоров, я бы сказал.

0 голосов
/ 18 марта 2011

Вы можете использовать glutIdleFunc, который всегда и везде вызывается - аналогично циклу while(game_is_running). То есть, какую бы логику вы не поместили бы в этот цикл while, вы могли бы включить обратный вызов для glutIdleFunc. Вы можете избежать использования glutTimerFunc, отслеживая галочки самостоятельно, как в статье, которую вы связали (используя glutGet(GLUT_ELAPSED_TIME)).

...