В литературе по OpenGL распространены два подхода к анимации. Рассмотрим их на примере программы, рисующей вращающийся треугольник.
#include <GL/glut.h>
// Глобальные переменные:
int startTime; // начальный момент времени, мс
static GLfloat rot = 0.0f;
static GLfloat rotRate = 45.0f; // скорость вращения, градус/секунда
void initGraphics()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
// ###### другие команды инициализации графики ######
}
void display()
{
// Очищаем окно (буфер цвета)
glClear(GL_COLOR_BUFFER_BIT);
// Задаем перемещение и вращение фигуры
glLoadIdentity();
glTranslatef(0.0, 0.0, -3.0);
glRotatef(rot, 0.0, 0.0, 1.0);
// Рисуем треугольник
glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0);
glVertex3f(-1.0, -1.0, 0.0);
glColor3f(0.0, 1.0, 0.0);
glVertex3f(1.0, -1.0, 0.0);
glColor3f(0.0, 0.0, 1.0);
glVertex3f(0.0, 1.0, 0.0);
glEnd();
glFlush();
glutSwapBuffers();
}
void reshape(int width, int height)
{
// Предотвращаем деление на ноль
if (height <= 0) height = 1;
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0, (double)width/(double)height, 1.0, 500.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void keyboard(unsigned char key, int x, int y)
{
switch (key) {
case 27: // клавиша Escape
exit(0);
break;
default:
return;
}
// Принудительная перерисовка после нажатия клавиши
glutPostRedisplay();
}
void animate()
{
// Измеряем прошедшее время
int currTime = glutGet(GLUT_ELAPSED_TIME);
int elapsedTime = currTime - startTime;
// Вычисляем угол поворота треугольника
rot = (rotRate / 1000) * elapsedTime;
glutPostRedisplay();
}
int main(int argc, char **argv)
{
glutInit(&argc, argv);
// #1: Инициализация и создание окна GLUT
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
glutCreateWindow("Animation");
initGraphics();
// #2: Регистрация функций-обработчиков событий
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutIdleFunc(animate);
// Получаем значение времени старта, мс
startTime = glutGet(GLUT_ELAPSED_TIME);
// #3: Запуск основного цикла GLUT
glutMainLoop();
}

По структуре эта программа мало чем отличается от рассмотренных ранее. Подробного рассмотрения заслуживают только функции display() и animate().
В display() мы рисуем не готовую фигуру, а нечто новое, хотя и очень простое:
(:source lang=C++
// Рисуем треугольник
glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0);
glVertex3f(-1.0, -1.0, 0.0);
glColor3f(0.0, 1.0, 0.0);
glVertex3f(1.0, -1.0, 0.0);
glColor3f(0.0, 0.0, 1.0);
glVertex3f(0.0, 1.0, 0.0);
glEnd();
Перед тем как нарисовать, мы сдвигаем треугольник вглубь по оси `z` и поворачиваем вокруг оси `x` на величину, заданную переменной `rot`.
```C++
// Задаем перемещение и вращение фигуры
glLoadIdentity();
glTranslatef(0.0, 0.0, -3.0);
glRotatef(rot, 0.0, 0.0, 1.0);
Треугольник сдвигается затем, чтобы его можно было увидеть. Иначе бы он находился в плоскости z = 0, и не был бы виден при заданных параметрах перспективной проекции (хотя можно изменить эти параметры, не перемещая треугольник).
Посмотрим, как эта программа будет работать.
- Создается графическое окно с заданными размерами, расположением и заголовком.
- Запускается функция
reshape(), которая задает матрицы проекции и модели. - На экране отображается то, что указано в функции
display()— треугольник, повернутый на уголrot. - Поскольку никаких событий не происходит (точнее, нас они пока не интересуют), то запускается функция "времени бездействия" —
animate(). Она пересчитывает угол поворота треугольникаrotи принудительно обновляет содержимое окна, то есть опять вызываетdisplay(). Так возникает цикл анимации из шагов 3 и 4, который будет продолжаться до тех пор, пока мы не закроем приложение.
Обратите внимание на функцию glLoadIdentity(). Дело в том, что в OpenGL все изменения вносятся относительно текущего состояния. Например, если display() "задвигает" треугольник на 3 единицы вдоль оси z, то после первого вызова display() он окажется в плоскости z = -3, после второго — в z = -6, после третьего — в z = -9 и т. д. Если закомментировать glLoadIdentity(), то мы увидим удаляющийся от нас треугольник вращающийся все быстрей и быстрей. glLoadIdentity() возвращает модель к исходному состоянию. Кстати, если мы уберем из display очистку glClear(GL_COLOR_BUFFER_BIT), то будем наблюдать в окне не только нынешнее положение треугольника, но и всю их "историю".
В функции animate() для вычисления угла поворота rot используется время, прошедшее с запуска основного цикла GLUT — elapsedTime. Функция glutGet() с параметром GLUT_ELAPSED_TIME возвращает время (в миллисекундах), прошедшее с момента запуска GLUT (вызова glutInit()).
Отметим также реализацию выбора реакции на нажатие клавиш через switch..case, что позволяет гибко настраивать ее, добавляя блоки case:
void keyboard(unsigned char key, int x, int y)
{
switch (key) {
case 27: // клавиша Escape
exit(0);
break;
default:
return;
}
...
}
Основой рассмотренного выше подхода к анимации являлось использование функции "времени бездействия", заданной в glutIdleFunc(). Существует и другой подход, при котором перерисовка изображения выполняется периодически, по сигналу таймера.
Следующая программа делает то же, что и предыдущая, но другим способом, поэтому повторяющиеся фрагменты кода не показаны.
#include <GL/glut.h>
// Глобальные переменные:
...
unsigned timerMSecs = (unsigned) 1000/60; // время между запусками таймера, мс
...
void animate(int value)
{
// Измеряем прошедшее время
int currTime = glutGet(GLUT_ELAPSED_TIME);
int elapsedTime = currTime - startTime;
// Вычисляем угол поворота треугольника
rot = (rotRate / 1000) * elapsedTime;
glutPostRedisplay();
// Перезапускаем таймер
glutTimerFunc(timerMSecs, animate, 0);
}
int main(int argc, char **argv)
{
glutInit(&argc, argv);
// #1: Инициализация и создание окна GLUT
...
// #2: Регистрация функций-обработчиков событий
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutTimerFunc(timerMSecs, animate, 0);
// Получаем значение времени старта, мс
startTime = glutGet(GLUT_ELAPSED_TIME);
// #3: Запуск основного цикла GLUT
glutMainLoop();
}
Вместо glutIdleFunc() используется glutTimerFunc():
glutTimerFunc(unsigned int время_в_мс, функция_анимации, int значение)
время_в_мс означает время (в миллисекундах), через которое будет запущена функция_анимации; значение нас сейчас не интересует.
Таким образом, через timerMSecs после запуска основного цикла GLUT будет вызвана функция animate(), которая вычислит угол поворота, заставит перерисовать изображение и, наконец, — задаст, через сколько времени будет вновь вызвана функция анимации.
glutTimerFunc() запускает animate(), которая снова вызывает glutTimerFunc()... Не столкнулись ли мы с бесконечной рекурсией? Нет, в случае OpenGL это не так. Можно сказать, что glutTimerFunc() добавляет еще одну операцию к череде операций, выполняемых OpenGL: посчитать, перерисовать, вызвать через N миллисекунд заданную функцию.
Выбор промежутка времени между запусками таймера диктуется возможностями видеоадаптера.
unsigned timerMSecs = (unsigned) 1000/60; // время между запусками таймера, мс
В данном случае предполагается, что частота обновления экрана составляет 60 Гц (1/60 c). Если задать обновления более редкими, то мы увидим, что треугольник будет двигаться "рывками".
Комментарии
comments powered by Disqus