Библиотека 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.
Результатом выполнения программы будет окно с заданным заголовком.
В окне отображается содержимое области экрана, в которой оно появилось (левый верхний угол). Это произошло потому что не было выполнено каких-либо команд рисования, в частности, рисования фона окна заданным цветом.
Рисование в окне
Следующая программа рисует окно зеленого цвета.
#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
.
Функция 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()
необходима, поскольку сфера строится в начале координат, а плоскость проекции задается как 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