Когда я впервые начал изучать OpenGL с Python, моей главной целью было выяснить, как создать вращающийся куб. Я не думаю, что я одинок, поскольку это, кажется, вершина понимания основ OpenGL. В итоге я смонтировал это первое видео, включив в него все: от установки Python, PyOpenGL и PyGame до написания необходимого кода для создания вращающегося куба. Эта первая статья получилась довольно длинной, но мне хотелось включить сюда все, что касается данной темы.
Разобраться в OpenGL мне было довольно трудно. В основном, потому что большинство руководств по OpenGL с PyOpenGL, которые я смог найти, были явно написаны для тех, кто уже знал OpenGL. Я OpenGL не знал, так что это было серьезным препятствием для меня. Надеюсь, я смогу помочь вам понять эту тему намного быстрее, чем удалось мне.
Во-первых, PyOpenGL — это всего лишь в некотором роде оболочка Python вокруг OpenGL. Она позволяет манипулировать OpenGL при помощи Python. OpenGL — это межъязыковой API, поэтому вы можете перенести свои знания OpenGL на другие языки.
Как же работает OpenGL? Вы просто определяете объекты в пространстве. Например, для куба вы указываете его углы. Углы называются вершинами или нодами.
Как только вы определите вершины, вы сможете что-то с ними делать. В этом примере мы хотим провести между ними линии. Определение вершин в Python выполняется с помощью простого списка или кортежа. Затем вы можете заранее задать некоторые правила. Например, можно указать, какие вершины составляют поверхность и между какими вершинами находятся ребра или линии, которые мы хотим провести между вершинами.
Сделав это, вы готовы начать писать код OpenGL. Этот код помещается между выражениями glBegin
и glEnd
. В выражении glBegin
мы указываем тип кода, который мы собираемся передать. Обычно это константы, например GL_QUADS
или GL_LINES
. Они сообщают библиотеке OpenGL, каким образом она должна обрабатывать ваши выражения.
Итак, это была довольно общая концепция того, как работает OpenGL, давайте теперь продолжим и напишем код!
Для начала нам надо иметь вот что: Python, PyOpenGL, PyGame.
Если вы пользователь Windows, я настоятельно рекомендую загрузить PyGame и PyOpenGL из этого источника: Windows binaries for Python Modules. Обязательно сохраните эту ссылку, это крайне полезный сайт.
Загрузив все это, откройте вашу IDE и выполните следующий код:
import pygame import OpenGL
Если эти выражения не вызовут ошибок, значит вы готовы к дальнейшей работе. А если будут ошибки, значит что-то пошло не так. Как правило, ошибки возникают при несовпадении версий Python, PyGame или PyOpenGL. Также они могут возникнуть при несовпадении битовости Python и загружаемых в него библиотек. То есть, если вы используете 32-битный Python, вам нужно использовать 32-битные модули библиотек и так далее.
Даже если ваша операционная система — 64-битная, все равно может оказаться, что вы используете 32-битную версию Python. Мы настоятельно рекомендуем по возможности использовать 64-битный Python. Дело в том, что 32-битный Python может использовать только 2 ГБ оперативной памяти, а это существенное ограничение. Разумеется, если у вас 32-битная ОС, вы не можете использовать 64-битный Python.
Отлично, теперь перейдем непосредственно к коду! Если у вас еще сохранился код импорта, который мы только что запускали, сотрите его: мы начнем полностью с чистого листа.
Для начала произведем импорт необходимых библиотек:
import pygame from pygame.locals import * from OpenGL.GL import * from OpenGL.GLU import *
Мы импортируем все из PyGame, а затем все из PyGame.locals. Это вполне стандартный код для PyGame. Если вы хотите подробнее ознакомиться с данной библиотекой, то можете посмотреть другие статьи про нее на нашем сайте, например вот эту.
Затем мы импортируем OpenGL.GL и OpenGL.GLU. OpenGL.GL содержит в себе самые обычные функции библиотеки OpenGL, а вот OpenGL.GLU — более забавные.
Теперь вспомним про наши вершины:
vertices= ( (1, -1, -1), (1, 1, -1), (-1, 1, -1), (-1, -1, -1), (1, -1, 1), (1, 1, 1), (-1, -1, 1), (-1, 1, 1) )
Здесь мы определили координаты (x, y, z) каждой нашей вершины. Думаю, лучше всего представить это в относительных единицах. Попробуйте представить их в пространстве. Напоминаю, что у куба восемь вершин.
Далее нам надо задать ребра:
edges = ( (0,1), (0,3), (0,4), (2,1), (2,3), (2,7), (6,3), (6,4), (6,7), (5,1), (5,4), (5,7) )
Каждое ребро представлено кортежем, состоящим из двух чисел. Эти числа соответствуют номерам вершин, а ребро их соединяет. Как принято в Python, да и во многих других языках программирования, нумерация начинается с 0
. Соответственно, 0
обозначает вершину (1, -1, -1)
, и так далее.
Теперь, когда у нас все это есть, давайте поработаем над необходимым кодом для работы с OpenGL, чтобы фактически создать сам куб:
def Cube(): glBegin(GL_LINES) for edge in edges: for vertex in edge: glVertex3fv(vertices[vertex]) glEnd()
Как обычно, мы начинаем с задания функции. Поскольку это просто функция, которая будет содержать код OpenGL, мы начинаем этот код со следующего выражения: glBegin (GL_LINES)
. Это уведомляет OpenGL сначала о том, что мы собираемся бросить в нее какой-то код, а затем — о том, как надо обрабатывать этот код (это указывает аргумент GL_LINES
). В данном случае этот код будет рассматриваться как код для рисования линий.
Далее, мы итерируем по всем нашим ребрам (список edges
), а затем каждой вершине в ребре (их там две) мы ставим в соответствие вершину из нашего списка вершин vertices
(при помощи функции glVertex3fv
).
В конечном итоге в OpenGL передаются константы GL_LINES:
glVertex3fv((1, -1, -1)) glVertex3fv((1, 1, -1))
И так далее. OpenGL, зная, что мы хотим рисовать здесь линии, проведет их между этими точками.
После прохождения всех ребер работа функции заканчивается и мы вызываем glEnd()
, чтобы сообщить об этом OpenGL. Подобные открывающие и закрывающие команды используются в OpenGL постоянно.
Это все, что касается нашей функции. Она создает куб, но теперь мы хотим его отобразить и указать его перспективу в нашем окружении:
def main(): pygame.init() display = (800,600) pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
Это тоже очень типичный для Pygame код. Единственное существенное отличие здесь в том, что после параметра display
в функции pygame.display.set_mode
мы добавляем еще один параметр. На самом деле это константы, уведомляющие PyGame о том, что мы будем использовать код OpenGL. Константа DOUBLEBUF расшифровывается как двойной буфер. Она обозначает тип буфферизации, в котором есть два буфера для соответствия кадровой частоте монитора. Обратите внимание, что для разделения констант используется символ «|». Мы еще столкнемся с ним в дальнейшем.
Идем дальше. В теле главной функции находится следующий код:
gluPerspective(45, (display[0]/display[1]), 0.1, 50.0)
Функция gluPerspective
определяет перспективу, о чем, впрочем, несложно догадаться из ее названия. Ее первый параметр определяет угол поля зрения и выражается в градусах. Второй параметр — это соотношение сторон дисплея, ширина, деленная на высоту. Следующие два параметра — znear
и zfar
, которые представляют собой ближнюю и дальнюю плоскости отсечения.
Что, черт возьми, означает плоскость отсечения? Если вы похожи на меня, на данный момент это ничего вам не говорит. По сути, плоскость отсечения — это расстояние, на котором объект либо появляется, либо исчезает. Таким образом, объект будет виден только между этими двумя значениями, и оба значения должны быть положительными, потому что они связаны с вашей перспективой, а не с вашим фактическим местоположением в трехмерной среде.
Итак, у нас есть ближняя плоскость отсечения, находящаяся на расстоянии 0.1
единицы, и дальняя плоскость отсечения на расстоянии 50.0
единиц. Это станет более понятным несколько позже, как только мы отобразим куб и сможем контролировать свое местонахождение в 3D-среде. Именно тогда вы увидите плоскости отсечения в действии.
Идем дальше. У нас есть следующая функция:
glTranslatef(0.0,0.0, -5)
Функция glTranslatef
, цитируя дословно, «умножает текущую матрицу на матрицу перехода». Круто, но для меня это опять ничего не значит. Итак, с точки зрения непрофессионала, это в основном перемещает вас, то есть сдвигает параметры ваших координат — x, y и z. Таким образом, вышеуказанный код означает, что мы сдвигаемся на 5
единиц назад. Это делается для того, чтобы мы действительно могли видеть куб, когда создаем его. Иначе мы были бы слишком близко.
Теперь напишем наш типичный цикл для отслеживания событий в PyGame. Опять же, если вы хотите узнать больше, ознакомьтесь со статьями, которые уже упоминались ранее.
while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit()
Это простой цикл отслеживания событий в PyGame, который определяет возможность выхода. Иными словами, он отслеживает нажатие клавиши "x"
. Далее, под оператором while
продолжаем наш код:
glRotatef(1, 3, 1, 1) glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) Cube() pygame.display.flip() pygame.time.wait(10)
Функция glRotatef
умножает текущую матрицу на матрицу вращения. Ее параметрами являются угол вращения и координаты x, y, и z.
Затем у нас есть функция glClear
, работающая, как любая другая функция очистки. Мы указываем в ее параметрах пару констант, которые сообщают OpenGL, что именно мы очищаем.
Как только мы очистим «экран», мы опять вызовем нашу функцию Cube ()
.
После этого мы вызываем функцию pygame.display.flip ()
, которая обновляет наш экран.
И наконец, мы вставляем небольшую задержку при помощи функции pygame.time.wait (10)
.
Это все, что касается нашей главной функции main()
, и теперь мы просто вызываем ее в конце, чтобы все заработало. На случай, если вы где-то заблудились, вот весь наш код, собранный воедино:
import pygame from pygame.locals import * from OpenGL.GL import * from OpenGL.GLU import * verticies = ( (1, -1, -1), (1, 1, -1), (-1, 1, -1), (-1, -1, -1), (1, -1, 1), (1, 1, 1), (-1, -1, 1), (-1, 1, 1) ) edges = ( (0,1), (0,3), (0,4), (2,1), (2,3), (2,7), (6,3), (6,4), (6,7), (5,1), (5,4), (5,7) ) def Cube(): glBegin(GL_LINES) for edge in edges: for vertex in edge: glVertex3fv(verticies[vertex]) glEnd() def main(): pygame.init() display = (800,600) pygame.display.set_mode(display, DOUBLEBUF|OPENGL) gluPerspective(45, (display[0]/display[1]), 0.1, 50.0) glTranslatef(0.0,0.0, -5) while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() glRotatef(1, 3, 1, 1) glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) Cube() pygame.display.flip() pygame.time.wait(10) main()
Результат его выполнения должен быть следующим:
Отлично, поздравляем вас с созданием куба при помощи PyOpenGL! Очевидно, что вам еще многое предстоит узнать, и работа многих функций пока для вас до конца не понятна. Мы скоро рассмотрим их более подробно.
Следующая статья — Введение в OpenGL и PyOpenGL. Часть II: раскрашивание поверхностей вращающегося куба.