Date Редакция Категория comp Теги OpenGL / Cpp / GLUT

Библиотека OpenGL предоставляет интерфейс для работы с видеокартой, но в ней нет инструментов для создания графических окон и обработки событий (нажатий клавиш, движений мыши и пр.). Поэтому вместе с OpenGL мы будем использовать библиотеку GLUT (The OpenGL Utility Toolkit), содержащую необходимый минимум средств для создания графических приложений.

Создание окна приложения

Простейшее приложение, создающее окно с помощью GLUT, имеет вид:

#include <GL/glut.h>

int main(int argc, char **argv)
{
    // #1: Инициализация и создание окна GLUT
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(640, 320);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Window's Title");

    // #2: Регистрация функций-обработчиков событий

    // #3: Запуск основного цикла GLUT
    glutMainLoop();
}

Разберем его. В строке

    glutInit(&argc, argv);

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

    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(640, 320);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Window's Title");

Здесь указываются характеристики окна приложения, а также некоторые параметры OpenGL. Для окна указывается размер и позиция левого верхнего угла. GLUT_DOUBLE означает, что будет использоваться двойная буферизация; GLUT_RGB — что будет использоваться цветовая модель RGB.

В строке

    glutCreateWindow("Window's Title");

создается окно с заданными ранее характеристиками и заголовком "Window's Title".

Комментарий

    // #2: Регистрация функций-обработчиков событий

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

    glutMainLoop();

— запуск основного цикла работы приложения, построенного на базе GLUT.

Результатом выполнения программы будет окно с заданным заголовком.

Окно, созданное с помощью GLUT.

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

Рисование в окне

Следующая программа рисует окно зеленого цвета.

#include <GL/glut.h>

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    // здесь что-то рисуется

    glutSwapBuffers();
}

int main(int argc, char **argv)
{
    // #1: Инициализация и создание окна GLUT
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(640, 320);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Window's Title");

    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);

    // #2: Регистрация функций-обработчиков событий
    glutDisplayFunc(display);

    // #3: Запуск основного цикла GLUT
    glutMainLoop();
}

В ней появился обработчик события — функция display(), отвечающая за рисование

glutDisplayFunc(display);

Строка

glClearColor(0.0f, 1.0f, 0.0f, 1.0f);

задает цвет фона окна.

Формат функции:

glClearColor(красный_цвет, зеленый_цвет, синий_цвет, прозрачность);

Цвет задается вещественным числом в диапазоне от 0.0 до 1.0, то есть может быть практически любым.

Теперь у нас указан цвет фона и функция display(), которая будет его рисовать. В display() строка

glClear(GL_COLOR_BUFFER_BIT);

очищает буфер кадра указанным в glClearColor() цветом.

glutSwapBuffers();

выводит содержимое окна на экран, точнее — меняет местами содержимое заднего и переднего буферов.

Двойная буферизация, заданная параметром GLUT_DOUBLE в glutInitDisplayMode(), используется затем, чтобы избежать мерцания картинки. При этом содержимое одного из буферов (переднего) отображается на экране, в то время как изображение строится в другом (заднем) буфере. После того, как изображение будет построено, буферы меняются местами (swap buffers) и на экране отображается готовый рисунок.

Окно с заданным цветом фона.

Остается нарисовать что-нибудь в окне. Для этого в display() вместо комментария напишем

glutSolidSphere(0.1f, 50, 50); // сфера -- один из примитивов GLUT

В результате получим

Сфера.

Изменение размеров окна

Наша программа имеет две очевидные проблемы: сфера вовсе не сфера, а эллипсоид; она изменяется с изменением размеров окна.

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

glutReshapeFunc(reshape);

В нашем случае обработчиком служит функция reshape(). Она принимает два параметра — новую ширину и новую высоту окна:

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);
    gluLookAt(0, 0, 1, 0, 0, 0, 0, 1, 0);

    glMatrixMode(GL_MODELVIEW);
    // Следующие действия производятся с матрицей модели
    glLoadIdentity();
}

Разберем код этой функции.

Строка

    glViewport(0, 0, width, height);

задает область просмотра в виде прямоугольника с диагонально противоположными вершинами (x1;y1) и (x2;y2) или, в нашем случае, (0;0) и (width;height).

Отображение трехмерной сцены на плоскость (проектирование) выполняется с помощью матрицы проекций и ряда функций. Задаем единичную матрицу проекций

    glMatrixMode(GL_PROJECTION);
    // Следующие действия производятся с матрицей проекции
    glLoadIdentity();

Строка

   gluPerspective(60.0, (double)width/(double)height, 1.0, 500.0);

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

Функция gluPerspective() имеет формат:

gluPerspective(fovy,w/h,zNear,zFar): 

Ее параметры — угол обзора (fovy), коэффициент отношения ширины к высоте (w/h), а также ближняя (zNear) и дальняя (zFar) плоскости отсечения изображения. Наблюдателю будет видна сцена, находящаяся в промежутке от zNear до zFar, спроектированная на плоскость zNear.

Использование gluPerspective()

Функция gluLookAt() задает откуда и куда смотрит камера (или наблюдатель) и имеет следующий формат.

gluLookAt(eye_x, eye_y, eye_z, center_x, center_y, center_z, up_x, up_y, up_z);
  • (eye_x, eye_y, eye_z) — координаты точки, откуда направлена камера (точки зрения);
  • (center_x, center_y, center_z) — координаты точки наблюдения;
  • (up_x, up_y, up_z) — координаты вектора, задающего направление на верх камеры. По умолчанию камера расположена в начале координат и направлена в сторону отрицательных значений оси z; вверх направлена ось y.

Использование gluLookAt()

Вектор, задающий направление на верх камеры

В нашей программе gluLookAt() необходима, поскольку сфера строится в начале координат, а плоскость проекции задается как z = 1. В результате, при использовании точки наблюдения, заданной по умолчанию, камера оказалась бы "за спиной" плоскости проекции и мы бы ничего не увидели. Поэтому, перенесем точку наблюдения в (0, 0, 1):

gluLookAt(0, 0, 1, 0, 0, 0, 0, 1, 0);

Устанавливаем единичную матрицу модели:

    glMatrixMode(GL_MODELVIEW);
    // Следующие действия производятся с матрицей модели
    glLoadIdentity();

Матрица модели служит для преобразования объектов в трехмерном пространстве — переноса, поворота и т. п. В нашем случае объект никаким преобразованиям не подвергается.

Теперь соберем все вместе

#include <GL/glut.h>

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);

    glutSolidSphere(0.1f, 50, 50);       // сфера

    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);
    gluLookAt(0, 0, 1, 0, 0, 0, 0, 1, 0);

    glMatrixMode(GL_MODELVIEW);
    // Следующие действия производятся с матрицей модели
    glLoadIdentity();
}

int main(int argc, char **argv)
{
    // #1: Инициализация и создание окна GLUT
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(640, 320);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Window's Title");

    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);

    // #2: Регистрация функций-обработчиков событий
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);

    // #3: Запуск основного цикла GLUT
    glutMainLoop();
}

и получим

Изображение, полученное с учетом проекции и изменения размеров окна

Обработка событий мыши и клавиатуры

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

    glutKeyboardFunc(keyboard);

и код обработчика

void keyboard(unsigned char key, int x, int y)
{
    if (key == 27) exit(0); // 27 - код клавиши Esc
}

Аналогично, с помощью функций glutMouseFunc() и glutMotionFunc(), обрабатываются нажатия кнопок и перемещения мыши.

Анимация

Заставим изображаемый объект вращаться. Для этого нам понадобится новый примитив

    glutWireTeapot(0.3);       // чайник

(поскольку на сфере вращения не будет видно), и новый обработчик

    glutIdleFunc(idle);

Функция idle() будет вызываться во время простоя (когда не наступает других событий, обрабатываемых GLUT) и перерисовывать содержимое окна. Она имеет вид:

void idle()
{
    move += 1.0f;
    if (move > 1000) move = 0;

    cameraRotationAngleX = sin(rad * move);
    cameraRotationAngleZ = cos(rad * move);

    glutPostRedisplay();
}

Вместо того, чтобы вращать объект, мы вращаем камеру и заставляем GLUT перерисовывать содержимое окна, вызывая glutPostRedisplay().

Данные передаются между функциями при помощи глобальных переменных move, cameraRotationAngleX, cameraRotationAngleZ.

Функцию gluLookAt() пришлось переместить внутрь display(), т.к. положение камеры меняется при каждой перерисовке.

    gluLookAt(cameraRotationAngleX, 0, cameraRotationAngleZ, 0, 0, 0, 0, 1, 0);

Кадр анимации.

Полный код:

#include <GL/glut.h>
#include <math.h>

const float rad = 0.01745;
float move = 0;
float cameraRotationAngleX = sin(rad * move);
float cameraRotationAngleZ = cos(rad * move);

void display()
{
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();

    gluLookAt(cameraRotationAngleX, 0, cameraRotationAngleZ, 0, 0, 0, 0, 1, 0);

    glutWireTeapot(0.3);       // чайник

    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)
{
    if (key == 27) exit(0); // 27 - код клавиши Esc
}

void idle()
{
    move += 1.0f;
    if (move > 1000) move = 0;

    cameraRotationAngleX = sin(rad * move);
    cameraRotationAngleZ = cos(rad * move);

    glutPostRedisplay();
}

int main(int argc, char **argv)
{
    // #1: Инициализация и создание окна GLUT
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(640, 320);
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Window's Title");

    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);

    // #2: Регистрация функций-обработчиков событий
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutIdleFunc(idle);

    // #3: Запуск основного цикла GLUT
    glutMainLoop();
}


Комментарии

comments powered by Disqus