В предыдущей статье были рассмотрены основы работы с OpenGL в Python. Для вывода графики использовались встроенные функции модуля glut и фиксированный конвейер OpenGL без шейдеров. По просьбе пользователей habrahabr.ru, на базе предыдущего урока был создан шаблон PyOpenGL приложения, использующего шейдеры и буферные объекты.
Роскошной графики, как и в предыдущей статье, ожидать не стоит. Цель данной статьи — продемонстрировать возможность работы с шейдерами и буферными объектами с использованием модуля PyOpenGL.
Итак, для работы нам понадобятся:
- Интерпретатор языка Python (ссылка).
- Среда разработки PyCharm (ссылка) (или любая другая на ваш вкус, подойдет даже блокнот).
- Библиотека PyOpenGL (ссылка).
В среде разработке создадим и сохраним новый файл с кодом Python.
Для работы с 3D графикой (в частности, OpenGL) необходимо импортировать несколько модулей:
from OpenGL.GL import *
from OpenGL.GLUT import *
Модуль glut будет использован для создания окна и обработки нажатия клавиш. Дополнительно импортируем функцию random из одноименного модуля (пригодится для изменения цвета полигона):
from random import random
Подготовка.
Инициализируем режим отображения с использованием двойной буферизации и цветов в формате RGB (двойная буферизация позволяет избежать мерцания во время перерисовки экрана):
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
Зададим начальный размер окна (ширина, высота):
glutInitWindowSize(300, 300)
Укажем начальное положение окна относительно левого верхнего угла экрана:
glutInitWindowPosition(50, 50)
Выполним инициализацию OpenGl:
glutInit(sys.argv)
Создадим окно с заголовком «Shaders!»:
glutCreateWindow(b"Shaders!")
Определим процедуру, отвечающую за вывод графики на экран:
glutDisplayFunc(draw)
Определим процедуру, выполняющуюся при «простое» программы:
glutIdleFunc(draw)
Определяем процедуру, отвечающую за обработку специальных клавиш:
glutSpecialFunc(specialkeys)
Задаем серый цвет для очистки экрана:
glClearColor(0.2, 0.2, 0.2, 1)
До этого момента код был практически не отличим от использованного в предыдущей статье, но теперь все сильно меняется, в дело вступают шейдеры!
Для удобства создаем процедуру, подготавливающую шейдер к использованию:
# Процедура подготовки шейдера (тип шейдера, текст шейдера)
def create_shader(shader_type, source):
# Создаем пустой объект шейдера
shader = glCreateShader(shader_type)
# Привязываем текст шейдера к пустому объекту шейдера
glShaderSource(shader, source)
# Компилируем шейдер
glCompileShader(shader)
# Возвращаем созданный шейдер
return shader
С использованием процедуры create_shader создадим вершинный шейдер:
# Положение вершин не меняется
# Цвет вершины - такой же как и в массиве цветов
vertex = create_shader(GL_VERTEX_SHADER, """
varying vec4 vertex_color;
void main(){
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
vertex_color = gl_Color;
}""")
Таким же образом создадим фрагментный шейдер:
# Определяет цвет каждого фрагмента как "смешанный" цвет его вершин
fragment = create_shader(GL_FRAGMENT_SHADER, """
varying vec4 vertex_color;
void main() {
gl_FragColor = vertex_color;
}""")
Создаем пустой объект шейдерной программы:
program = glCreateProgram()
Приcоединяем вершинный и фрагментный шейдеры к программе:
glAttachShader(program, vertex)
glAttachShader(program, fragment)
«Собираем» шейдерную программу:
glLinkProgram(program)
Сообщаем OpenGL о необходимости использовать данную шейдерную программу при выводе объектов на экран:
glUseProgram(program)
Теперь нам нужно определится что и каким цветом выводить на экран. Для этого создадим два массива. Первый массив — с координатами вершин (три вершины по три координаты):
pointdata = [[0, 0.5, 0], [-0.5, -0.5, 0], [0.5, -0.5, 0]]
Второй массив — с цветами для каждой вершины (по одному цвету для каждой):
pointcolor = [[1, 1, 0], [0, 1, 1], [1, 0, 1]]
Эти два массива можно объединить в один, но для наглядности и удобства восприятия они разнесены. На этом подготовительные мероприятия завершены и можно запустить основной цикл программы:
glutMainLoop()
Далее рассмотрим процедуры, отвечающие за обработку нажатий клавиш и, собственно, вывод объектов на экран.
Обработчик нажатий клавиш.
За обработку нажатий клавиш в нашей программе отвечает процедура specialkeys. В коде specialkeys в зависимости от того, какая стрелка на клавиатуре была нажата, мы, с использованием процедуры glRotatef, осуществляем поворот по осям x или y, по часовой стрелке или в обратном направлении, на 5 градусов. При нажатии клавиши END заполняем массив pointcolor случайными числами в диапазоне 0 до 1, тем самым меняя цвет отображаемого полигона. Код процедуры specialkeys:
# Процедура обработки специальных клавиш
def specialkeys(key, x, y):
# Сообщаем о необходимости использовать глобального массива pointcolor
global pointcolor
# Обработчики специальных клавиш
if key == GLUT_KEY_UP: # Клавиша вверх
glRotatef(5, 1, 0, 0) # Вращаем на 5 градусов по оси X
if key == GLUT_KEY_DOWN: # Клавиша вниз
glRotatef(-5, 1, 0, 0) # Вращаем на -5 градусов по оси X
if key == GLUT_KEY_LEFT: # Клавиша влево
glRotatef(5, 0, 1, 0) # Вращаем на 5 градусов по оси Y
if key == GLUT_KEY_RIGHT: # Клавиша вправо
glRotatef(-5, 0, 1, 0) # Вращаем на -5 градусов по оси Y
if key == GLUT_KEY_END: # Клавиша END
# Заполняем массив pointcolor случайными числами в диапазоне 0-1
pointcolor = [[random(), random(), random()], [random(), random(), random()], [random(), random(), random()]]
Процедура перерисовки.
За перерисовку в нашей программе отвечает процедура draw. Первым делом очищаем экран и заполняем его серым цветом:
glClear(GL_COLOR_BUFFER_BIT)
Включаем использование массивов вершин и цветов:
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
Далее укажем OpenGL где взять массив вершин, для этого используем процедуру glVertexPointer:
glVertexPointer(3, GL_FLOAT, 0, pointdata)
Первый параметр данной процедуры определяет сколько используется координат для одной вершины, второй параметр определяет тип данных для каждой координаты, третий параметр определяет смещение между вершинами в массиве. Если вершины идут одна за другой, то смещение 0. Четвертый параметр указывает на первую координату первой вершины в массиве.
Аналогично укажем OpenGL где взять массив цветов:
glColorPointer(3, GL_FLOAT, 0, pointcolor)
Все необходимые данные указаны, осталось только все нарисовать. С использованием процедуры glDrawArrays мы можем вывести все содержимое массивов на экран за один проход:
glDrawArrays(GL_TRIANGLES, 0, 3)
Первый параметр данной процедуры определяет то, какой тип примитивов будет использоваться при выводе объектов на экран (треугольники, точки, линии и др.), второй параметр должен указывать на начальный индекс в указанных массивах, третьим параметром мы указываем количество рисуемых примитивов (в нашем случае это 3 вершины — 9 координат).
Отключаем использование массивов вершин и цветов и выводим все нарисованное в памяти на экран:
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_COLOR_ARRAY)
glutSwapBuffers()
В результате в окне программы мы наблюдаем треугольник с плавными переходами цветов. Треугольник можно вращать с использованием клавиш «стрелок». При нажатии кнопки END происходит смена цвета треугольника на случайный.
# -*- coding: utf-8 -*-
# Импортируем все необходимые библиотеки:
from OpenGL.GL import *
from OpenGL.GLUT import *
#import sys
# Из модуля random импортируем одноименную функцию random
from random import random
# объявляем массив pointcolor глобальным (будет доступен во всей программе)
global pointcolor
# Процедура обработки специальных клавиш
def specialkeys(key, x, y):
# Сообщаем о необходимости использовать глобального массива pointcolor
global pointcolor
# Обработчики специальных клавиш
if key == GLUT_KEY_UP: # Клавиша вверх
glRotatef(5, 1, 0, 0) # Вращаем на 5 градусов по оси X
if key == GLUT_KEY_DOWN: # Клавиша вниз
glRotatef(-5, 1, 0, 0) # Вращаем на -5 градусов по оси X
if key == GLUT_KEY_LEFT: # Клавиша влево
glRotatef(5, 0, 1, 0) # Вращаем на 5 градусов по оси Y
if key == GLUT_KEY_RIGHT: # Клавиша вправо
glRotatef(-5, 0, 1, 0) # Вращаем на -5 градусов по оси Y
if key == GLUT_KEY_END: # Клавиша END
# Заполняем массив pointcolor случайными числами в диапазоне 0-1
pointcolor = [[random(), random(), random()], [random(), random(), random()], [random(), random(), random()]]
# Процедура подготовки шейдера (тип шейдера, текст шейдера)
def create_shader(shader_type, source):
# Создаем пустой объект шейдера
shader = glCreateShader(shader_type)
# Привязываем текст шейдера к пустому объекту шейдера
glShaderSource(shader, source)
# Компилируем шейдер
glCompileShader(shader)
# Возвращаем созданный шейдер
return shader
# Процедура перерисовки
def draw():
glClear(GL_COLOR_BUFFER_BIT) # Очищаем экран и заливаем серым цветом
glEnableClientState(GL_VERTEX_ARRAY) # Включаем использование массива вершин
glEnableClientState(GL_COLOR_ARRAY) # Включаем использование массива цветов
# Указываем, где взять массив верши:
# Первый параметр - сколько используется координат на одну вершину
# Второй параметр - определяем тип данных для каждой координаты вершины
# Третий парметр - определяет смещение между вершинами в массиве
# Если вершины идут одна за другой, то смещение 0
# Четвертый параметр - указатель на первую координату первой вершины в массиве
glVertexPointer(3, GL_FLOAT, 0, pointdata)
# Указываем, где взять массив цветов:
# Параметры аналогичны, но указывается массив цветов
glColorPointer(3, GL_FLOAT, 0, pointcolor)
# Рисуем данные массивов за один проход:
# Первый параметр - какой тип примитивов использовать (треугольники, точки, линии и др.)
# Второй параметр - начальный индекс в указанных массивах
# Третий параметр - количество рисуемых объектов (в нашем случае это 3 вершины - 9 координат)
glDrawArrays(GL_TRIANGLES, 0, 3)
glDisableClientState(GL_VERTEX_ARRAY) # Отключаем использование массива вершин
glDisableClientState(GL_COLOR_ARRAY) # Отключаем использование массива цветов
glutSwapBuffers() # Выводим все нарисованное в памяти на экран
# Здесь начинется выполнение программы
# Использовать двойную буферезацию и цвета в формате RGB (Красный Синий Зеленый)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
# Указываем начальный размер окна (ширина, высота)
glutInitWindowSize(300, 300)
# Указываем начальное
# положение окна относительно левого верхнего угла экрана
glutInitWindowPosition(50, 50)
# Инициализация OpenGl
glutInit(sys.argv)
# Создаем окно с заголовком "Shaders!"
glutCreateWindow(b"Shaders!")
# Определяем процедуру, отвечающую за перерисовку
glutDisplayFunc(draw)
# Определяем процедуру, выполняющуюся при "простое" программы
glutIdleFunc(draw)
# Определяем процедуру, отвечающую за обработку клавиш
glutSpecialFunc(specialkeys)
# Задаем серый цвет для очистки экрана
glClearColor(0.2, 0.2, 0.2, 1)
# Создаем вершинный шейдер:
# Положение вершин не меняется
# Цвет вершины - такой же как и в массиве цветов
vertex = create_shader(GL_VERTEX_SHADER, """
varying vec4 vertex_color;
void main(){
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
vertex_color = gl_Color;
}""")
# Создаем фрагментный шейдер:
# Определяет цвет каждого фрагмента как "смешанный" цвет его вершин
fragment = create_shader(GL_FRAGMENT_SHADER, """
varying vec4 vertex_color;
void main() {
gl_FragColor = vertex_color;
}""")
# Создаем пустой объект шейдерной программы
program = glCreateProgram()
# Приcоединяем вершинный шейдер к программе
glAttachShader(program, vertex)
# Присоединяем фрагментный шейдер к программе
glAttachShader(program, fragment)
# "Собираем" шейдерную программу
glLinkProgram(program)
# Сообщаем OpenGL о необходимости использовать данную шейдерну программу при отрисовке объектов
glUseProgram(program)
# Определяем массив вершин (три вершины по три координаты)
pointdata = [[0, 0.5, 0], [-0.5, -0.5, 0], [0.5, -0.5, 0]]
# Определяем массив цветов (по одному цвету для каждой вершины)
pointcolor = [[1, 1, 0], [0, 1, 1], [1, 0, 1]]
# Запускаем основной цикл
glutMainLoop()
и немного видео:
Автор: Ti_Fix