OpenGL
Знакомство с OpenGL нужно начать с того, что OpenGL — это спецификация. Т.е. OpenGL лишь определяет набор обязательных возможностей. Реализация же зависит от конкретной платформы.
OpenGL является кроссплатформенным, независимым от языка программирования API для работы с графикой. OpenGL — низкоуровневый API, поэтому для работы с ним неплохо иметь некоторое представление о графике в целом и знать основы линейной алгебры.
Именования
Скажем пару слов об именовании функций в OpenGL. Во-первых имена всех функций, предоставляемых непосредственно OpenGL, начинаются с приставки gl. Во-вторых функции, задающие некоторый параметр, характеризующийся набором чисел (например координату или цвет), имеют суффикс вида [число параметров + тип параметров + представление параметров].
- Число параметров — указывает число принимаемых параметров. Принимает следующие значения: 1, 2, 3, 4
- Тип параметров — указывает тип принимаемых параметров. Возможны следующие значения: b, s, i, f, d, ub, us, ui. Т.е. byte (char в C, 8-битное целое число), short (16-битное целое число), int (32-битное целое число), float (число с плавающей запятой), double (число с плавающей запятой двойной точности), unsigned byte, unsigned short, unsigned int (последние три — беззнаковые целые числа)
- Представление параметров — указывает в каком виде передаются параметры, если каждое число по отдельности, то ничего не пишется, если же параметры передаются в виде массива, то к названию функции дописывается буква v
Пример: glVertex3iv задает координату вершины, состоящую из трех целых чисел, передаваемых в виде указателя на массив.
Графика
Все графические объекты в OpenGL представляют собой набор точек, линий и многоугольников. Существует 10 различных примитивов, при помощи которых строятся все объекты. Как двухмерные, так и трехмерные. Все примитивы в свою очередь задаются точками — вершинами.
- GL_POINTS — каждая вершина задает точку
- GL_LINES — каждая отдельная пара вершин задает линию
- GL_LINE_STRIP — каждая пара вершин задает линию (т.е. конец предыдущей линии является началом следующей)
- GL_LINE_LOOP — аналогично предыдущему за исключением того, что последняя вершина соединяется с первой и получается замкнутая фигура
- GL_TRIANGLES — каждая отдельная тройка вершин задает треугольник
- GL_TRIANGLE_STRIP — каждая следующая вершина задает треугольник вместе с двумя предыдущими (получается лента из треугольников)
- GL_TRIANGLE_FAN — каждый треугольник задается первой вершиной и последующими парами (т.е. треугольники строятся вокруг первой вершины, образуя нечто похожее на диафрагму)
- GL_QUADS — каждые четыре вершины образуют четырехугольник
- GL_QUAD_STRIP — каждая следующая пара вершин образует четырехугольник вместе с парой предыдущих
- GL_POLYGON — задает многоугольник с количеством углов равным количеству заданных вершин
Для задания примитива используется конструкция glBegin (тип_примитива)…glEnd (). Вершины задаются glVertex*. Вершины задаются против часовой стрелки. Координаты задаются от верхнего левого угла окна. Цвет вершины задается командой glColor*. Цвет задается в виде RGB или RGBA. Команда glColor* действует на все вершины, что идут после до тех пор, пока не встретится другая команда glColor* или же на все, если других команд glColor* нет.
Вот код рисующий квадрат с разноцветными вершинами:
- glBegin(GL_QUADS);
- glColor3f(1.0, 1.0, 1.0);
- glVertex2i(250, 450);
- glColor3f(0.0, 0.0, 1.0);
- glVertex2i(250, 150);
- glColor3f(0.0, 1.0, 0.0);
- glVertex2i(550, 150);
- glColor3f(1.0, 0.0, 0.0);
- glVertex2i(550, 450);
- glEnd();
Основы программы на OpenGL
Для платформонезависимой работы с окнами можно использовать библиотеку GLUT. GLUT упрощает работу с OpenGL.
Для инициализации GLUT в начале программы надо вызвать glutInit (&argc, argv). Для задания режима дисплея вызывается glutInitDisplayMode (режим), где режим может принимать следующие значения:
- GLUT_RGBA — включает четырехкомпонентный цвет (используется по умолчанию)
- GLUT_RGB — то же, что и GLUT_RGBA
- GLUT_INDEX — включает индексированный цвет
- GLUT_DOUBLE — включает двойной экранный буфер
- GLUT_SINGLE — включает одиночный экранный буфер (по умолчанию)
- GLUT_DEPTH — включает Z-буфер (буфер глубины)
- GLUT_STENCIL — включает трафаретный буфер
- GLUT_ACCUM — включает буфер накопления
- GLUT_ALPHA — включает альфа-смешивание (прозрачность)
- GLUT_MULTISAMPLE — включает мультисемплинг (сглаживание)
- GLUT_STEREO — включает стерео-изображение
Для выбора нескольких режимов одновременно нужно использовать побитовое ИЛИ '|'. Например: glutInitDisplayMode (GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH) включает двойную буферизацию, Z-буфер и четырехкомпонентный цвет. Размеры окна задаются glutInitWindowSize (ширина, высота). Его позиция — glutInitWindowPosition (х, у). Создается окно функцией glutCreateWindow (заголовок_окна).
GLUT реализует событийно-управляемый механизм. Т.е. есть главный цикл, который запускается после инициализации, и в нем уже обрабатываются все объявленные события. Например нажатие клавиши на клавиатуре или движение курсора мыши и т.д. Зарегистрировать функции-обработчики событий можно при помощи следующих команд:
- void glutDisplayFunc (void (*func) (void)) — задает функцию рисования изображения
- void glutReshapeFunc (void (*func) (int width, int height)) — задает функцию обработки изменения размеров окна
- void glutVisibilityFunc (void (*func)(int state)) — задает функцию обработки изменения состояния видимости окна
- void glutKeyboardFunc (void (*func)(unsigned char key, int x, int y)) — задает функцию обработки нажатия клавиш клавиатуры (только тех, что генерируют ascii-символы)
- void glutSpecialFunc (void (*func)(int key, int x, int y)) — задает функцию обработки нажатия клавиш клавиатуры (тех, что не генерируют ascii-символы)
- void glutIdleFunc (void (*func) (void)) — задает функцию, вызываемую при отсутствии других событий
- void glutMouseFunc (void (*func) (int button, int state, int x, int y)) — задает функцию, обрабатывающую команды мыши
- void glutMotionFunc (void (*func)(int x, int y)) — задает функцию, обрабатывающую движение курсора мыши, когда зажата какая-либо кнопка мыши
- void glutPassiveMotionFunc (void (*func)(int x, int y)) — задает функцию, обрабатывающую движение курсора мыши, когда не зажато ни одной кнопки мыши
- void glutEntryFunc (void (*func)(int state)) — задает функцию, обрабатывающую движение курсора за пределы окна и его возвращение
- void glutTimerFunc (unsigned int msecs, void (*func)(int value), value) — задает функцию, вызываемую по таймеру
Затем можно запускать главный цикл glutMainLoop ().
Первая программа
Теперь мы знаем основы работы с OpenGL. Можно написать простую программу для закрепления знаний.
Начнем с того, что нужно подключить заголовочный файл GLUT:
- #if defined(linux) || defined(_WIN32)
- #include <GL/glut.h> /*Для Linux и Windows*/
- #else
- #include <GLUT/GLUT.h> /*Для Mac OS*/
- #endif
Теперь мы уже знаем, что писать в main. Зарегистрируем два обработчика: для рисования содержимого окна и обработки изменения его размеров. Эти два обработчика по сути используются в любой программе, использующей OpenGL и GLUT.
- int main (int argc, char * argv[])
- {
- glutInit(&argc, argv);
- glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA); /*Включаем двойную буферизацию и четырехкомпонентный цвет*/
- glutInitWindowSize(800, 600);
- glutCreateWindow(«OpenGL lesson 1»);
- glutReshapeFunc(reshape);
- glutDisplayFunc(display);
- glutMainLoop();
- return 0;
- }
Теперь надо написать функцию-обработчик изменений размеров окна. Зададим область вывода изображения размером со все окно при помощи команды glViewport (х, у, ширина, высота). Затем загрузим матрицу проекции glMatrixMode (GL_PROJECTION), заменим ее единичной glLoadIdentity () и установим ортогональную проекцию. И наконец загрузим модельно-видовую матрицу glMatrixMode (GL_MODELVIEW) и заменим ее единичной.
В итоге получим:
- void reshape(int w, int h)
- {
- glViewport(0, 0, w, h);
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- gluOrtho2D(0, w, 0, h);
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- }
Осталось только написать функцию рисования содержимого окна. Рисовать будем тот квадрат, что я приводил выше в качестве примера. Добавить придется совсем немного кода. Во-первых перед рисованием надо очистить различные буфера при помощи glClear (режим). Используется также как и glutInitDisplayMode. Возможные значения:
- GL_COLOR_BUFFER_BIT — для очистки буфера цвета
- GL_DEPTH_BUFFER_BIT — для очистки буфера глубины
- GL_ACCUM_BUFFER_BIT — для очистки буфера накопления
- GL_STENCIL_BUFFER_BIT — для очистки трафаретного буфера
В нашем случае нужно очистить только буфер цвета, т.к. другие мы не используем. Во-вторых после рисования нужно попросить OpenGL сменить экранные буфера при помощи glutSwapBuffers (), ведь у нас включена двойная буферизация. Все рисуется на скрытом от пользователя буфере и затем происходит смена буферов. Делается это для получения плавной анимации и для того, чтобы не было эффекта мерцания экрана.
Получаем:
- void display()
- {
- glClear(GL_COLOR_BUFFER_BIT);
- glBegin(GL_QUADS);
- glColor3f(1.0, 1.0, 1.0);
- glVertex2i(250, 450);
- glColor3f(0.0, 0.0, 1.0);
- glVertex2i(250, 150);
- glColor3f(0.0, 1.0, 0.0);
- glVertex2i(550, 150);
- glColor3f(1.0, 0.0, 0.0);
- glVertex2i(550, 450);
- glEnd();
- glutSwapBuffers();
- }
Итог
Все! Можно компилировать. Должно получиться что-то вроде этого:
Весь код целиком (для кто не осилил статью).
В принципе ничего сложного в этом нет, по крайней мере если вы уже сталкивались с графикой до этого.
OpenGL — удобный инструмент для создания кроссплатформенных приложений, использующий графику. OpenGL легко использовать с тем языком программирования, который вам более удобен. Привязки к OpenGL есть для множества популярных языков, таких как C, C++, C#, Java, Python, Perl, VB и других. Посмотреть информацию о них можно на официальном сайте OpenGL.