Пользователь
0,1
рейтинг
23 декабря 2014 в 23:04

Разработка → PyOpenGL для начинающих и немного новогоднего настроения tutorial

image

Благодаря своей гибкости и простоте, язык Python позволяет легко и быстро писать целый ряд приложений и утилит. Мною данный язык, в основном, используется для написания небольших скриптов, облегчающих выполнение различных задач, связанных с системным администрированием. Как оказалось, Python можно использовать и для не совсем «традиционных» задач, например, для вывода 3D графики. Об этом и будет мой небольшой предновогодний пост.


В этой статье я постараюсь показать, насколько просто работать с OpenGL в Python. Рисовать на экране мы будем новогоднюю 3D елку. Елка будет довольно схематичная, поэтому, если вы ожидали от поста роскошную графику с шейдерами, можете дальше не читать, вам будет не интересно.

Для работы с «непитоновскими» библиотеками (например, OpenGL) необходимы модули, обеспечивающие возможность вызова функций библиотеки непосредственно из программы на языке Python. Библиотека PyOpenGL — модуль, позволяющий в программах на языке Python легко работать с функциями OpenGL, GLU и GLUT, а также с рядом расширений OpenGL.

Итак, для работы нам понадобятся:
  • Интерпретатор языка Python (ссылка).
  • Среда разработки PyCharm (ссылка) (или любая другая на ваш вкус, подойдет даже блокнот).
  • Библиотека PyOpenGL (ссылка).

В среде разработке создадим и сохраним новый файл с кодом Python (имеет расширение .py).

Для работы с 3D графикой (в частности, OpenGL) необходимо импортировать несколько модулей:
from OpenGL.GL import * 
from OpenGL.GLU import * 
from OpenGL.GLUT import * 

Мы будем, насколько возможно, пользоваться функциями из модуля glut, чтобы не писать «лишний» код и не изобретать очередные велосипеды.
Инициализируем режим отображения с использованием двойной буферизации и цветов в формате RGB (двойная буферизация позволяет избежать мерцания во время перерисовки экрана):
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB) 

Зададим начальный размер окна (ширина, высота):
glutInitWindowSize(300, 300)

Укажем начальное положение окна относительно левого верхнего угла экрана:
glutInitWindowPosition(50, 50)

Выполним инициализацию OpenGl:
glutInit(sys.argv)

Создадим окно с заголовком «Happy New Year!»:
glutCreateWindow(b"Happy New Year!")

Запустим основной цикл программы:
glutMainLoop()

Если запустить программу на выполнение, то мы увидим пустое окно с заголовком «Happy New Year!». Это замечательно, но не хватает главного — елки! Поэтому не будем останавливаться на достигнутом и пойдем дальше.

Перед тем как начать рисовать елку, необходимо провести ряд подготовительных мероприятий или, другими словами, выполнить инициализацию. Для этого создадим отдельную процедуру и не забудем вызвать её до запуска основного цикла программы. В нашей программе процедура инициализации выглядит следующим образом:
# Процедура инициализации
def init():
    global xrot         # Величина вращения по оси x
    global yrot         # Величина вращения по оси y
    global ambient      # Рассеянное освещение
    global greencolor   # Цвет елочных иголок
    global treecolor    # Цвет елочного ствола
    global lightpos     # Положение источника освещения

    xrot = 0.0                          # Величина вращения по оси x = 0
    yrot = 0.0                          # Величина вращения по оси y = 0
    ambient = (1.0, 1.0, 1.0, 1)        # Первые три числа - цвет в формате RGB, а последнее - яркость
    greencolor = (0.2, 0.8, 0.0, 0.8)   # Зеленый цвет для иголок
    treecolor = (0.9, 0.6, 0.3, 0.8)    # Коричневый цвет для ствола
    lightpos = (1.0, 1.0, 1.0)          # Положение источника освещения по осям xyz

    glClearColor(0.5, 0.5, 0.5, 1.0)                # Серый цвет для первоначальной закраски
    gluOrtho2D(-1.0, 1.0, -1.0, 1.0)                # Определяем границы рисования по горизонтали и вертикали
    glRotatef(-90, 1.0, 0.0, 0.0)                   # Сместимся по оси Х на 90 градусов
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient) # Определяем текущую модель освещения
    glEnable(GL_LIGHTING)                           # Включаем освещение
    glEnable(GL_LIGHT0)                             # Включаем один источник света
    glLightfv(GL_LIGHT0, GL_POSITION, lightpos)     # Определяем положение источника света

Переменные xrot и yrot определяют угол вращения по осям x и y (это понадобится нам в дальнейшем для того, чтобы иметь возможность осмотреть елку со всех сторон). Массив ambient определяет параметры рассеянного освещения: цвет и яркость. Далее задаются две переменные (greencolor, treecolor), определяющие зеленый цвет для иголок и коричневый цвет для ствола.

Функцией glClearColor, определяется цвет, которым будет закрашиваться экран перед каждым новым циклом перерисовки. Функцией gluOrtho2D определяем границы рисования по горизонтали и вертикали. С использованием функции glRotatef смещаемся по оси X на 90 градусов, так нам будет лучше видно елку. Функцией glLightModelfv устанавливаем рассеянную модель освещения. Включаем освещение командой glEnable(GL_LIGHTING). Просто включить освещение недостаточно, нужно добавить хотя бы один источник освещения. Делаем это функцией glEnable(GL_LIGHT0). И, напоследок, отодвинем подальше только что созданный источник света:
glLightfv(GL_LIGHT0, GL_POSITION, lightpos)

На этом инициализацию OpenGL можно считать завершенной. Осталось только одно подготовительное мероприятие: нам нужно создать процедуру, обрабатывающую нажатия клавиш, и сообщить glut о необходимости её использовать (для этого перед запуском основного цикла программы выполним функцию glutSpecialFunc(specialkeys). Сама процедура specialkeys выглядит следующим образом:
# Процедура обработки специальных клавиш
def specialkeys(key, x, y):
    global xrot
    global yrot
    # Обработчики для клавиш со стрелками
    if key == GLUT_KEY_UP:      # Клавиша вверх
        xrot -= 2.0             # Уменьшаем угол вращения по оси X
    if key == GLUT_KEY_DOWN:    # Клавиша вниз
        xrot += 2.0             # Увеличиваем угол вращения по оси X
    if key == GLUT_KEY_LEFT:    # Клавиша влево
        yrot -= 2.0             # Уменьшаем угол вращения по оси Y
    if key == GLUT_KEY_RIGHT:   # Клавиша вправо
        yrot += 2.0             # Увеличиваем угол вращения по оси Y

    glutPostRedisplay()         # Вызываем процедуру перерисовки

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

Все, теперь можно приступать к главному — рисованию елки! Как и в случае с функцией specialkeys сообщим glut о том, какую процедуру использовать для перерисовки экрана: glutDisplayFunc(draw). Сама процедура draw выглядит следующим образом:
# Процедура перерисовки
def draw():
    global xrot
    global yrot
    global lightpos
    global greencolor
    global treecolor

    glClear(GL_COLOR_BUFFER_BIT)                                # Очищаем экран и заливаем серым цветом
    glPushMatrix()                                              # Сохраняем текущее положение "камеры"
    glRotatef(xrot, 1.0, 0.0, 0.0)                              # Вращаем по оси X на величину xrot
    glRotatef(yrot, 0.0, 1.0, 0.0)                              # Вращаем по оси Y на величину yrot
    glLightfv(GL_LIGHT0, GL_POSITION, lightpos)                 # Источник света вращаем вместе с елкой

    # Рисуем ствол елки
    # Устанавливаем материал: рисовать с 2 сторон, рассеянное освещение, коричневый цвет
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, treecolor)
    glTranslatef(0.0, 0.0, -0.7)                                # Сдвинемся по оси Z на -0.7
    # Рисуем цилиндр с радиусом 0.1, высотой 0.2
    # Последние два числа определяют количество полигонов
    glutSolidCylinder(0.1, 0.2, 20, 20)
    # Рисуем ветки елки
    # Устанавливаем материал: рисовать с 2 сторон, рассеянное освещение, зеленый цвет
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, greencolor)
    glTranslatef(0.0, 0.0, 0.2)                                 # Сдвинемся по оси Z на 0.2
    # Рисуем нижние ветки (конус) с радиусом 0.5, высотой 0.5
    # Последние два числа определяют количество полигонов
    glutSolidCone(0.5, 0.5, 20, 20)
    glTranslatef(0.0, 0.0, 0.3)                                 # Сдвинемся по оси Z на -0.3
    glutSolidCone(0.4, 0.4, 20, 20)                             # Конус с радиусом 0.4, высотой 0.4
    glTranslatef(0.0, 0.0, 0.3)                                 # Сдвинемся по оси Z на -0.3
    glutSolidCone(0.3, 0.3, 20, 20)                             # Конус с радиусом 0.3, высотой 0.3

    glPopMatrix()                                               # Возвращаем сохраненное положение "камеры"
    glutSwapBuffers()                                           # Выводим все нарисованное в памяти на экран

Функция glClear(GL_COLOR_BUFFER_BIT) используется для заливки экрана серым цветом. Пара функций glPushMatrix() и glPopMatrix() позволяет нам вращать только елку. Функция glLightfv(GL_LIGHT0, GL_POSITION, lightpos) «вращает» источник освещения вместе с елкой, благодаря этому, она остается «статично» освещенной. Функция glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color) устанавливает двусторонний режим рисования, рассеянное освещение и задает цвет, которым рисуется объект. Иголки мы рисуем с использованием функции glutSolidCone(0.5, 0.5, 20, 20), а ствол елки с использованием функции glutSolidCylinder(0.1, 0.2, 20, 20). Первые два параметра этих функции определяют радиус и высоту, а последние два — количество элементов, из которых состоят фигуры (полигонов). После того, как все части елки нарисованы в памяти видеокарты, вызовом функции glutSwapBuffers() выводим их на экран.

Весь код программы:
# Импортируем все необходимые библиотеки:
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import sys

# Объявляем все глобальные переменные
global xrot         # Величина вращения по оси x
global yrot         # Величина вращения по оси y
global ambient      # рассеянное освещение
global greencolor   # Цвет елочных иголок
global treecolor    # Цвет елочного стебля
global lightpos     # Положение источника освещения


# Процедура инициализации
def init():
    global xrot         # Величина вращения по оси x
    global yrot         # Величина вращения по оси y
    global ambient      # Рассеянное освещение
    global greencolor   # Цвет елочных иголок
    global treecolor    # Цвет елочного ствола
    global lightpos     # Положение источника освещения

    xrot = 0.0                          # Величина вращения по оси x = 0
    yrot = 0.0                          # Величина вращения по оси y = 0
    ambient = (1.0, 1.0, 1.0, 1)        # Первые три числа цвет в формате RGB, а последнее - яркость
    greencolor = (0.2, 0.8, 0.0, 0.8)   # Зеленый цвет для иголок
    treecolor = (0.9, 0.6, 0.3, 0.8)    # Коричневый цвет для ствола
    lightpos = (1.0, 1.0, 1.0)          # Положение источника освещения по осям xyz

    glClearColor(0.5, 0.5, 0.5, 1.0)                # Серый цвет для первоначальной закраски
    gluOrtho2D(-1.0, 1.0, -1.0, 1.0)                # Определяем границы рисования по горизонтали и вертикали
    glRotatef(-90, 1.0, 0.0, 0.0)                   # Сместимся по оси Х на 90 градусов
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient) # Определяем текущую модель освещения
    glEnable(GL_LIGHTING)                           # Включаем освещение
    glEnable(GL_LIGHT0)                             # Включаем один источник света
    glLightfv(GL_LIGHT0, GL_POSITION, lightpos)     # Определяем положение источника света


# Процедура обработки специальных клавиш
def specialkeys(key, x, y):
    global xrot
    global yrot
    # Обработчики для клавиш со стрелками
    if key == GLUT_KEY_UP:      # Клавиша вверх
        xrot -= 2.0             # Уменьшаем угол вращения по оси Х
    if key == GLUT_KEY_DOWN:    # Клавиша вниз
        xrot += 2.0             # Увеличиваем угол вращения по оси Х
    if key == GLUT_KEY_LEFT:    # Клавиша влево
        yrot -= 2.0             # Уменьшаем угол вращения по оси Y
    if key == GLUT_KEY_RIGHT:   # Клавиша вправо
        yrot += 2.0             # Увеличиваем угол вращения по оси Y

    glutPostRedisplay()         # Вызываем процедуру перерисовки


# Процедура перерисовки
def draw():
    global xrot
    global yrot
    global lightpos
    global greencolor
    global treecolor

    glClear(GL_COLOR_BUFFER_BIT)                                # Очищаем экран и заливаем серым цветом
    glPushMatrix()                                              # Сохраняем текущее положение "камеры"
    glRotatef(xrot, 1.0, 0.0, 0.0)                              # Вращаем по оси X на величину xrot
    glRotatef(yrot, 0.0, 1.0, 0.0)                              # Вращаем по оси Y на величину yrot
    glLightfv(GL_LIGHT0, GL_POSITION, lightpos)                 # Источник света вращаем вместе с елкой

    # Рисуем ствол елки
    # Устанавливаем материал: рисовать с 2 сторон, рассеянное освещение, коричневый цвет
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, treecolor)
    glTranslatef(0.0, 0.0, -0.7)                                # Сдвинемся по оси Z на -0.7
    # Рисуем цилиндр с радиусом 0.1, высотой 0.2
    # Последние два числа определяют количество полигонов
    glutSolidCylinder(0.1, 0.2, 20, 20)
    # Рисуем ветки елки
    # Устанавливаем материал: рисовать с 2 сторон, рассеянное освещение, зеленый цвет
    glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, greencolor)
    glTranslatef(0.0, 0.0, 0.2)                                 # Сдвинемся по оси Z на 0.2
    # Рисуем нижние ветки (конус) с радиусом 0.5, высотой 0.5
    # Последние два числа определяют количество полигонов
    glutSolidCone(0.5, 0.5, 20, 20)
    glTranslatef(0.0, 0.0, 0.3)                                 # Сдвинемся по оси Z на -0.3
    glutSolidCone(0.4, 0.4, 20, 20)                             # Конус с радиусом 0.4, высотой 0.4
    glTranslatef(0.0, 0.0, 0.3)                                 # Сдвинемся по оси Z на -0.3
    glutSolidCone(0.3, 0.3, 20, 20)                             # Конус с радиусом 0.3, высотой 0.3

    glPopMatrix()                                               # Возвращаем сохраненное положение "камеры"
    glutSwapBuffers()                                           # Выводим все нарисованное в памяти на экран


# Здесь начинается выполнение программы
# Использовать двойную буферизацию и цвета в формате RGB (Красный, Зеленый, Синий)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
# Указываем начальный размер окна (ширина, высота)
glutInitWindowSize(300, 300)
# Указываем начальное положение окна относительно левого верхнего угла экрана
glutInitWindowPosition(50, 50)
# Инициализация OpenGl
glutInit(sys.argv)
# Создаем окно с заголовком "Happy New Year!"
glutCreateWindow(b"Happy New Year!")
# Определяем процедуру, отвечающую за перерисовку
glutDisplayFunc(draw)
# Определяем процедуру, отвечающую за обработку клавиш
glutSpecialFunc(specialkeys)
# Вызываем нашу функцию инициализации
init()
# Запускаем основной цикл
glutMainLoop()


Результат выполнения программы(елка во всей красе):

image

image

и немного видео:



Используя рассмотренный в посте функционал вполне можно рисовать не только новогодние елки, но и трехмерные графики, и модели различных систем.

Надеюсь, данная статья окажется для кого-то полезной и подтолкнет к дальнейшему изучению языка Python и спецификации OpenGL.
С наступающим Новым годом, товарищи!!!

P.S. Ссылка на следующий пост о PyOpenGL с шейдерами.
@Ti_Fix
карма
30,2
рейтинг 0,1
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (11)

  • +7
    А вы в курсе, что уже почти 2015 год и используемый вами opengl 3.0 устарел лет на 7?
    И начиная чуть ли не с opengl 3.2 (2009г.) нужно включать специальный режим совместимости, чтобы это старьё заработало (возможно, он включается где-то в glutInit)
    • +1
      Устарел неверное слово, правильно говорить разделился на 2 ветки, режим ядра и режим совместимости с первоначальным стандартом. Режим ядра штука хорошая, но он как ассемблер требует написание всего с нуля, там нет ни матриц ни понятного интуитивно функционала, поэтому для познания основ 3D не особо подходит, да и под питоном возникнет рад дополнительных сложностей связанных с манипуляцией структурированными данными. Код выше представленной ёлочки растянулся бы раз в 5-10 =) Да и статьи бы как таковой небыло, потому что автор до сих пор ломал голову над шейдерами, матрицами и математикой построения цилиндра.

      И да всех с наступающим, вот вам от меня Ёлочка =)
      sysky.in/mrgobus/three.js/elka.html
      • 0
        Отличная елка!
      • 0
        «deprecated» — значит deprecated.
        устаревший; не рекомендуемый; сомнительный.
        (лес.) «с гнилой сердцевиной»

        «написание с нуля» требуется ровно на столько же, насколько для и ёлочки требуется glBegin/glEnd и смена цвета карандашика, с которым черепашка ползает от вертекса к вертексу.

        glut — это библиотека, работающая на стороне пользовательского кода, а нечасть opengl api.
        Под новые стандарты есть новые библиотеки. В частности — glm. Вот на питоне развечто glm нету, это да. Зато есть numpy.

        А за семь лет подобных статей написано уже стопицот. От автора каждой «hello, ёлочка». Смысла в стопицот первой особо нету.
        А вот по новому api какраз нифига нет, кроме opengl-tutorial.org
        И как прикрутить его к питону, к numpy — совершенно не очевидно.
  • +2
    В курсе что устарел (но не смотря на это без проблем работает и на современном железе), но для того, чтобы пример получился насколько возможно простым, использовал старый API. PyOpenGl дает возможность использовать и современный API с шейдерами и отрисовкой всего за один проход, но пример в таком случае (на мой взгляд) получился бы слишком усложненным и непонятным для новичков.
    • +3
      Зря вы так, новые версии — не значит усложнение для новичков.
      • +3
        Думаю, стоит прислушаться к Ваши словам и попробовать написать вторую версию этой программа на современном OpenGL. Спасибо за комментарий. Если у меня получится задуманное, обязательно сделаю отдельный пост.
        • 0
          *программы
        • 0
          Успехов в начинаниях!
  • 0
    А почему не поворачивается ножкой к камере?
    • 0
      Мы вращаем объекты используя последовательность вызовов glRotatef по различным осям. Это дает не совсем тот результат, который можно ожидать (матрицы вращения накладываются друг на друга). Наиболее правильно осуществлять вращение трехмерных объектов с использованием кватернионов (Статья).

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.