Как стать автором
Обновить

Кроссплатформенные OpenGL + Python при помощи Kivy

Время на прочтение3 мин
Количество просмотров6.7K

Предыстория: будучи наивным чукотским программистом, я думал: "питон такой кроссплатформенный, напишу игрушку для сына, запущу на планшетике, пусть играется". В результате две недели ушло на попытки натыкать решение по переезду с PyOpenGL+pygame на kivy, так как внятного примера использования OpenGL с kivy не нашел. Возможно, кому-то мой опыт поможет сэкономить время.

Примерная схема процесса
Примерная схема процесса

Дисклеймер:

  1. Никого не призываю разрабатывать на python приложения с 3д-графикой под андроид. Это не самый шустрый вариант. Но если очень хочется, то можно читать дальше.

  2. В kivy есть встроенное-своё-родное решение Mesh, которое умеет в 3д-графику. С примерами тоже не всё так просто. Я предпочел голый OpenGL.

  3. Возможно, я открыл Америку в попытках найти Индию. Надеюсь, более опытные товарищи поправят и дополнят.

Перед тем, как начать работать с 3D графикой в kivy, стоит почитать про библиотеку вообще и про её установку (например, мануалы рекомендуют использовать виртуальную среду).

В чем проблема? Описание функций OpenGL разработчики kivy коварно прячут на своём официальном сайте. Вопрос только в том, как заставить это работать.

За исключением нескольких нюансов, переезд с PyOpenGL+pygame на kivy касательно использования функций собственно OpenGL заключается в замене строчек:

from OpenGL.GL import *
from OpenGL.GL.shaders import *

на:

from kivy.graphics.opengl import *

Нюансы: PyOpenGL работает с массивами numpy напрямую, в kivy приходится преобразовывать их функцией tobytes(). Для загрузки текстур в kivy используется glPixelStorei и glTexImage2D вместо glTexStorage2D и glTexSubImage2D. Шейдеры под андроид должны быть под версию 2 (без in, out и прочих излишеств) и иметь в начале код:

#ifdef GL_ES
    precision highp float;
#endif

Далее самое интересное - это всё надо как-то подключить к выводу на экран. В связке PyOpenGL+pygame код имеет такую структуру:

#Было:
import pygame
from pygame.locals import *

def init():
    pygame.init()
    pygame.display.set_mode((Width, Height), HWSURFACE | OPENGL | DOUBLEBUF)
    ''' дальше идут функции, инициализирующие работу с PyOpenGL '''
    
def main():
    init()
    while True:
        ''' меняем вершины и запускаем отрисовку '''
        pygame.display.flip()

В kivy надо создать класс приложения на основе класса App, в котором необходимо сослаться на виджет, унаследованный от класса Widget. Кроме того, нам понадобится объект Window, который автоматически создается при работе с kivy с учетом используемой операционной системы. Базовый класс у Window - WindowBase.

Таким образом структура приложения будет примерно такой:

#Стало:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.base import EventLoop
from kivy.clock import Clock

def init():
    ''' почти те же функции, инициализирующие работу с OpenGL '''
init()

class CustomWidget(Widget):
    def __init__(self, **kwargs):
        super(CustomWidget, self).__init__(**kwargs)
    def update_glsl(self, nap):
        ''' здесь меняем вершины и запускаем отрисовку '''
        Window.flip()

class MainApp(App):
    def build(self):
        root = CustomWidget()
        EventLoop.ensure_window()
        return root

    def on_start(self):
        Clock.schedule_interval(self.root.update_glsl, 40 ** -1) #это наш желательный FPS

if __name__ == '__main__':
    MainApp().run()

Этот код уже покажет картинку (если добавить соответствующий функционал OpenGL), но картинка будет мерзко мигать. Это происходит потому, что у объекта Window есть свой вызов функции flip(), который вставляет чёрный экран в наш вывод. Этот вызов описан в упомянутом классе WindowBase. Добавим следующий код, отключающий лишний флип, в описание класса MainApp:

def passFunc(W):
    pass

class MainApp(App):
    def build(self):
        root = CustomWidget()
        EventLoop.ensure_window()
        
        #вот тут отключаем лишний флип:
        Window.on_flip = lambda W = Window: passFunc(W)
        
        return root

Что дальше? После заполнения пробелов в этом коде, между программистом и apk-файлом остаётся только билдозер. Вот мануал, которого достаточно даже для непродвинутого юзера вроде меня (см. также подпись к видео, там есть скрипт, сильно упрощающий работу):

Кроме того в spec-файле рекомендую включить полноэкранный режим для пущего фпс.

Example вот. Код рисует инопланетянина с логотипа андроида (как на КДПВ), позволяет его вращать с помощью мышки/сенсора.

А что там всё-таки с производительностью? Ну такое. Написал небольшую игрушку - змейку в трехмерном пространстве. FPS на самсунг А51 болтается между 15 и 25. Видео записано с телефона:

Сын, между тем, отказался играться в моё поделие, ну хотя бы смотрит с интересом. Плоские рисованные игры двухлетке, оказывается, лучше заходят. Вот ради чего всё начиналось:

Стиль рубленой змеи - пара скриншотов с этапа отладки как дань уважения Джеки Чану:
Авангард 1
Авангард 1
Авангард 2
Авангард 2
При отладке текстур возникает желание использовать настроечную таблицу от старого телевещания
При отладке текстур возникает желание использовать настроечную таблицу от старого телевещания
Теги:
Хабы:
Всего голосов 5: ↑5 и ↓0+5
Комментарии2

Публикации

Истории

Работа

Swift разработчик
25 вакансий
iOS разработчик
22 вакансии
Data Scientist
85 вакансий
Python разработчик
131 вакансия

Ближайшие события

19 сентября
CDI Conf 2024
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн