X11 и OpenGL

Перед вами простое OpenGL-приложение в системе X Window. С помощью клавиш управления курсором можно вращать сцену, а нажатие на любую другую клавишу приведет к закрытию приложения.
О проекте
В данном случае проект представляет из себя отправную точку для создания других проектов на базе OpenGL. Кроме того, в небольшом исходнике показаны некоторые принципы обработки событий клавиатуры.
Лично у меня последний вопрос вызвал затруднения в плане сопоставления кода нажатой клавиши и соответствующей константы в keysymdef.h. По началу я просто вывел значения, что были возвращены обработчиком, в терминал, а затем использовал их в конструкции switch .. case. Но, как оказалось, существует способ обойтись без «магических» чисел в коде.
Базовый исходник взят с khronos.org
Код
Заголовочные файлы
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <X11/X.h> #include <X11/keysymdef.h> #include <X11/Xlib.h> #include <GL/gl.h> #include <GL/glx.h> #include <GL/glu.h>
stdio.h и stdlib.h нам понадобятся для вывода текста в терминал, а также мы будем использовать функцию exit. math.h содержит функции синуса и косинуса, которые помогут организовать вращение сцены. Далее включены заголовочные файлы для систем X11 и OpenGL соответственно.
Глобальные переменные
Display* dpy; Window root; GLint att[] = {GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None}; XVisualInfo* vi; Colormap cmap; XSetWindowAttributes swa; Window win; GLXContext glc; XWindowAttributes gwa; XEvent xev;
В плане глобальных переменных нет ничего нового по сравнению с оригинальным исходником.
Функции рисования
Подготовка сцены
void DrawPrepare(float c) { glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(40.0, 1.0, 1.0, 10.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(2.5*cos(c), 0.0, 2.5*sin(c), 0.0, 0.0, 0.0, 0.0, -1.0, 0.0); };
Функция DrawPrepare как раз и будет осуществлять поворот сцены. Вернее даже не самой сцены, а камеры вокруг неё. Тепеь по порядку о каждой функции.
glClearColor устанавливает цвет для фона. В данном случае это черный цвет. Аргументы функции — это компоненты модели RGBA.
glClear заполняет выбранным цветом фон окна. Но помимо флага для очистки фона и заполнения цветом (GL_COLOR_BUFFER_BIT) имеется флаг GL_DEPTH_BUFFER_BIT. Последний нам нужен для того, чтобы объекты на заднем плане не оказались нарисованы поверх тех, что на переднем.
Отмечу, что система OpenGL имеет свои внутренние состояния. То есть существует набор функций для установки внутренних параметров и набор функций для действий с этими параметрами.
Одна из таких функций — glMatrixMode. Её назначение — это выбор текущей матрицы для преобразований. Флаг GL_PROJECTION соответствует матрице, определяющей параметры видимого пространства. То есть насколько далеко видит камера (дальняя отсекающая плоскость), минимальное расстояние до видимых объектов (ближняя отсекающая плоскость), форма видимого пространства (пирамида или параллелепипед).
В данной графической системе матриц несколько. В этой статье мы коснемся двух из них. Это GL_PROJECTION и GL_MODELVIEW.
Так как выбор какой-либо из матриц не меняет значения компонентов этой матрицы, то результат всех предыдущих преобразований сохраняется. Если, например, применить преобразование соответствующее увеличению координаты X на 1, то при 5-и повторениях игрового цикла координата увеличится уже на 5 по ставнению с исходным значением. Таким образом, на начальном этапе формирования сцены, необходимо определить текущую матрицу как единичную, что и делает glLoadIdentity.
gluPerspective позволяет определить видимое пространство как пирамиду, в вершине которой находится наблюдатель и смотрит в строну основания. Первый параметр здесь — это угол обзора в градусах, второй — соотношение сторон видимого поля, третий — расстояние до ближней отсекающей плоскости, четвертый — расстояние до дальней отсекающей плоскости. Результат действия этой функции выражается в преобразовании всё той же матрицы.
Далее выбираем матрицу, соответствующую отображаемой сцене (GL_MODELVIEW) и снова, как и в предыдущем случае, определяем её как единичную.
А вот теперь функцией gluLookAt осуществляем преобразование, соответствющее переносу и развороту камеры в нужном направлении. Здесь первые три параметра — это координаты камеры, а следующие три — это координаты точки, на которую камера смотрит. В OpenGL ось Y направлена вверх по умолчанию, поэтому с помощью трех последних аргументов мы переворачиваем камеру с ног на голову. Сделано это для удобства, так как обычно ось Y направлена вниз, когда дело касается разработки игр. За счет функций sin и cos в координатах камеры мы будем осуществлять вращение вокруг оси Y. Коэффициент 2,5 определяет расстояние от наблюдателя до оси Y.
Отрисовка куба
void DrawBlock(float x, float y, float size) { glBegin(GL_QUADS); glColor3f(1.0, 0.0, 0.0); glVertex3f(x, y, 0.0); glColor3f(0.0, 1.0, 0.0); glVertex3f(x+size, y, 0.0); glColor3f(0.0, 0.0, 1.0); glVertex3f(x+size, y+size, 0.0); glColor3f(1.0, 1.0, 0.0); glVertex3f(x, y+size, 0.0); glEnd(); glBegin(GL_QUADS); glColor3f(1.0, 0.0, 0.0); glVertex3f(x, y, 0.0); glColor3f(0.0, 1.0, 0.0); glVertex3f(x, y, size); glColor3f(0.0, 0.0, 1.0); glVertex3f(x+size, y, size); glColor3f(1.0, 1.0, 0.0); glVertex3f(x+size, y, 0.0); glEnd(); glBegin(GL_QUADS); glColor3f(1.0, 0.0, 0.0); glVertex3f(x+size, y, 0.0); glColor3f(0.0, 1.0, 0.0); glVertex3f(x+size, y, size); glColor3f(0.0, 0.0, 1.0); glVertex3f(x+size, y+size, size); glColor3f(1.0, 1.0, 0.0); glVertex3f(x+size, y+size, 0.0); glEnd(); glBegin(GL_QUADS); glColor3f(1.0, 0.0, 0.0); glVertex3f(x, y, size); glColor3f(0.0, 1.0, 0.0); glVertex3f(x, y, 0.0); glColor3f(0.0, 0.0, 1.0); glVertex3f(x, y+size, 0.0); glColor3f(1.0, 1.0, 0.0); glVertex3f(x, y+size, size); glEnd(); glBegin(GL_QUADS); glColor3f(1.0, 0.0, 0.0); glVertex3f(x+size, y, size); glColor3f(0.0, 1.0, 0.0); glVertex3f(x, y, size); glColor3f(0.0, 0.0, 1.0); glVertex3f(x, y+size, size); glColor3f(1.0, 1.0, 0.0); glVertex3f(x+size, y+size, size); glEnd(); glBegin(GL_QUADS); glColor3f(1.0, 0.0, 0.0); glVertex3f(x+size, y+size, 0.0); glColor3f(0.0, 1.0, 0.0); glVertex3f(x+size, y+size, size); glColor3f(0.0, 0.0, 1.0); glVertex3f(x, y+size, size); glColor3f(1.0, 1.0, 0.0); glVertex3f(x, y+size, 0.0); glEnd(); };
Функция DrawBlock рисует куб путем формирования его граней по отдельности. В качестве аргументов мы указываем координаты x и y одной из вершин, а также длину ребра — size.
Функция main
int main(int argc, char* argv[]) { dpy = XOpenDisplay(NULL); if(dpy == NULL) { printf("\n\tCannot connect to X server\n\n"); exit(0); }; root = DefaultRootWindow(dpy); vi = glXChooseVisual(dpy, 0, att); if(vi == NULL) { printf("\n\tno appropriate visual found\n\n"); exit(0); } else { printf("\n\tvisual %p selected\n", (void*)vi->visualid); }; cmap = XCreateColormap(dpy, root, vi->visual, AllocNone); swa.colormap = cmap; swa.event_mask = KeyPressMask; win = XCreateWindow(dpy, root, 0, 0, 600, 600, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &swa); XMapWindow(dpy, win); XStoreName(dpy, win, "OpenGL and X"); glc = glXCreateContext(dpy, vi, NULL, GL_TRUE); glXMakeCurrent(dpy, win, glc); glEnable(GL_DEPTH_TEST); float c = 0.0, dc = 0.1; while(1) { if(XCheckTypedWindowEvent(dpy, win, KeyPress, &xev)) { KeySym sym = XLookupKeysym(&xev.xkey, 0); switch(sym) { case XK_Right: c += dc; break; case XK_Left: c -= dc; break; default: glXMakeCurrent(dpy, None, NULL); glXDestroyContext(dpy, glc); XDestroyWindow(dpy, win); XCloseDisplay(dpy); exit(0); break; }; }; XGetWindowAttributes(dpy, win, &gwa); glViewport(0, 0, gwa.width, gwa.height); DrawPrepare(c); for(float y=-0.5; y<=0.5; y+=0.1) for(float x=-0.5; x<=0.5; x+=0.1) DrawBlock(x, y, 0.09); glXSwapBuffers(dpy, win); }; };
Выделенные строки — это внесенные изменения в первоначальный исходник.
События окна
То, какие события мы собираемся обрабатывать, указываются в переменой event_mask. Так как перерисовка сцены осуществляется в цикле и независимо от того, изменили мы размер окна или нет, флаг ExposureMask был убран.
Заголовок окна
Смена заголовка осуществляется с помощью XStoreName.
z-buffer
Для того, чтобы флаг GL_DEPTH_BUFFER_BIT повлиял на отображение сцены, необходимо активировать соответствующее состояние графической системы, вызвав процедуру glEnable(GL_DEPTH_TEST).
Параметры поворота сцены
float c = 0.0 , dc = 0.1 — величины угла поворота в радианах и шага изменения этого угла соответственно.
Обработка событий клавиатуры
Для таких целей я решил выбрать функцию XCheckTypedWindowEvent, так как XNextEvent останавливает цикл, ожидая получения какого-нибудь из указанных ранее событий. С XCheckTypedWindowEvent такого не происходит. Это критичный момент, учитывая, что игровая «жизнь» далеко не всегда застывает, ожидая реакции игрока. Чтобы использовать константы, определенные в keysymdef.h, необходимо получить соответствующее значение с помощью XLookupKeysym.
Стена из блоков
Два вложенных цикла for формируют стену из блоков. Блоки располагаются с шагом в 0,1 и размером 0,09 (чтобы был зазор)
Исходники проекта
Скачать [6]
Добавить комментарий