Vývojáři používající programovací jazyk Python mohou při tvorbě multimediálních aplikací použít hned několik knihoven a dokonce i ucelených frameworků. Pravděpodobně nejznámější knihovou z této oblasti je knihovna Pygame, kterou jsme si již stručně představili v loňském seriálu. Ovšem k dispozici jsou i další knihovny, především pak knihovna nazvaná Pyglet, mezi jejíž přednosti patří elegantně vyřešená vazba na grafickou knihovnu OpenGL. Právě vlastnosti této knihovny si postupně ukážeme v tomto seriálu.
Obsah
1. Multimediální knihovna Pyglet
2. Instalace knihovny Pyglet do Fedory 25
3. První demonstrační příklad: prázdné černé okno
4. Úprava skriptu umožňující přímé spuštění demonstračního příkladu
5. Systém událostí (events) a automatické překreslení okna
6. Vykreslení textového návěští
7. Specifikace úchytů (anchors) textových návěští
8. Volání funkcí OpenGL přes rozhraní knihovny Pyglet
9. Zjednodušení volání funkcí OpenGL
10. Základní vlastnosti grafických primitiv v OpenGL
11. Vlastnosti plošných útvarů (polygonů), včetně trojúhelníků
12. Barevný trojúhelník aneb změna stavu OpenGL
13. Repositář s demonstračními příklady
1. Multimediální knihovna Pyglet
Vloni jsme se v seriálu o programovacích jazycích a knihovnách určených pro výuku základů počítačové grafiky mj. zabývali i projektem pojmenovaným Pygame, což je knihovna určená pro programovací jazyk Python, která interně volá funkce nativní knihovny SDL (Simple DirectMedia Layer) a několika dalších podpůrných knihoven. Myšlenka, na níž je projekt Pygame postaven, je v mnoha ohledech podobná myšlence taktéž popsaného systému LÖVE – implementace nízkoúrovňových operací nechť je vytvořena a optimalizována odborníky v nízkoúrovňových programovacích jazycích C a C++. Pokud budou tyto operace implementovány dostatečně rychle, je již možné zbytek hry či multimediální aplikace naprogramovat ve vysokoúrovňovém jazyku Python (v případě LÖVE pak jazyka Lua). A ukazuje se, že je tato myšlenka – a obecně systém rozdělení aplikace mezi dva programovací jazyky (kompilovaný a skriptovací) – poměrně úspěšná, neboť v Pygame již vzniklo mnoho kvalitních her.
Obrázek 1: Ukázka velmi jednoduché hry naprogramované s využitím knihovny LÖVE.
Výše stručně představená knihovna Pygame je kvůli své relativně těsné vazbě na nativní knihovnu SDL určena především pro tvorbu dvoudimenzionálních (2D) her, tj. například různých strategií (realtime i tahových), plošinovek, RPG apod. Ve chvíli, kdy je zapotřebí vytvořit plnohodnotnou trojrozměrnou hru, je možné namísto Pygame použít buď projekt PyOpenGL (rozhraní pro OpenGL) či další knihovnu určenou pro použití s programovacím jazykem Python. Tato knihovna se jmenuje Pyglet a mezi její základní vlastnosti patří poměrně úzká vazba na knihovnu OpenGL a tedy i nepřímo na grafické akcelerátory. Kromě vazby na knihovnu OpenGL však v Pyglet mohou programátoři najít i další zajímavé a užitečné moduly, například pro práci s hudbou a se zvuky (ALSA, OpenAL, DirectSound), podporu pro různé formáty uložení hudby (zejména OGG/Vorbis), podporu pro načítání a zobrazování animací uložených v DivX, AVI, MPEG, H.263 atd.
Obrázek 2: Logo multimediální knihovny Pyglet.
2. Instalace knihovny Pyglet do Fedory 25
Před vyzkoušením demonstračních příkladů popsaných v navazujících kapitolách je samozřejmě nutné nejdříve knihovnu Pyglet nainstalovat. Pokud používáte systém Fedora 24 či Fedora 25, je instalace velmi snadná a může být provedena jedním či dvěma příkazy. Nejdříve je nutné nainstalovat vlastní knihovnu Pyglet, a to následujícím způsobem (příkaz musíte spustit s rootovskými právy, tj. přes sudo či su):
dnf install python-pyglet
Průběh instalace:
Last metadata expiration check: 0:10:20 ago on Thu Mar 30 13:36:08 2017.
Dependencies resolved.
================================================================================
Package Arch Version Repository Size
================================================================================
Installing:
python-pyglet noarch 1.2.1-4.fc25 beaker-Fedora-Everything 1.2 M
python2-pillow x86_64 3.4.2-1.fc25 beaker-Fedora-Everything 594 k
Transaction Summary
================================================================================
Install 2 Packages
Total download size: 1.8 M
Installed size: 9.7 M
Is this ok [y/N]:
Downloading Packages:
(1/2): python2-pillow-3.4.2-1.fc25.x86_64.rpm 522 kB/s | 594 kB 00:01
(2/2): python-pyglet-1.2.1-4.fc25.noarch.rpm 1.1 MB/s | 1.2 MB 00:01
--------------------------------------------------------------------------------
Total 1.6 MB/s | 1.8 MB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Installing : python2-pillow-3.4.2-1.fc25.x86_64 1/2
Installing : python-pyglet-1.2.1-4.fc25.noarch 2/2
Verifying : python-pyglet-1.2.1-4.fc25.noarch 1/2
Verifying : python2-pillow-3.4.2-1.fc25.x86_64 2/2
Installed:
python-pyglet.noarch 1.2.1-4.fc25 python2-pillow.x86_64 3.4.2-1.fc25
Complete!
Ve druhém kroku je vhodné ověřit, zda je nainstalována i knihovna GLU a na ní závislé balíčky:
<strong>python</strong>
Python 2.7.13 (default, Jan 12 2017, 17:59:37)
[GCC 6.3.1 20161221 (Red Hat 6.3.1-1)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyglet
>>> pyglet.window.Window()
Pokud se vypíše chybové hlášení, je nutné doinstalovat výše zmíněnou knihovnu GLU:
dnf install libGLU
3. První demonstrační příklad: prázdné černé okno
První demonstrační příklad naprogramovaný s využitím knihovny Pyglet je velmi jednoduchý, protože se v něm pouze otevře prázdné okno, které je možné zavřít klávesou ESC. Celý skript napsaný v Pythonu obsahuje tři příkazy. Prvním příkazem importujeme všechny funkce, metody a objekty z modulu nazvaného pyglet. Druhým příkazem se vytvoří nové okno s velikostí 640×480 pixelů (právě zde dochází k testu, zda je vůbec možno okno otevřít a jestli jsou k dispozici všechny potřebné knihovny). Titulek okna bude obsahovat text „Pyglet library“. Třetí příkaz slouží k inicializaci vnitřního stavu knihovny Pyglet a ke spuštění aplikace:
import pyglet
window = pyglet.window.Window(width=640,
height=480,
caption="Pyglet library")
pyglet.app.run()
Povšimněte si, jakým způsobem jsou předány parametry okna – všechny parametry jsou pojmenovány (width=640), takže není nutné složitě zkoumat pořadí parametrů, zbytečně předávat nepoužívané parametry atd.
Spuštění prvního demonstračního příkladu provedeme snadno:
python 01_empty_window.py
Obrázek 3: Prázdné okno vykreslené prvním demonstračním příkladem.
4. Úprava pro přímé spuštění příkladu
Předchozí demonstrační příklad bylo nutné spouštět nepřímo přes interpret programovacího jazyka Python. Pokud se však má s využitím knihovny Pyglet vytvářet běžná aplikace reprezentovaná ikonou na ploše nebo v menu desktopového prostředí, může být užitečnější skript upravit takovým způsobem, aby ho bylo možné spustit automaticky a přímo zadáním jeho jména. To vyžaduje nepatrnou úpravu skriptu a nastavení příznaku „execute“. Úprava skriptu spočívá v přidání prvního řádku začínajícího na takzvaný „shebang“, za nímž následuje specifikace interpretru:
#!/usr/bin/env python
import pyglet
window = pyglet.window.Window(width=640,
height=480,
caption="Pyglet library")
pyglet.app.run()
Dále je nutné nastavit příznak „execute“, který umožní přímé spuštění skriptu. Nastavení příznaku se provede příkazem chmod:
chmod u+x 02_empty_window_script
Po těchto dvou úpravách se spuštění druhého demonstračního příkladu provede příkazem:
./02_empty_window_script
Samozřejmě je stále možné použít původní způsob spuštění:
python 02_empty_window_script
Obrázek 4: Prázdné okno vykreslené druhým demonstračním příkladem (totožný obsah s příkladem prvním).
5. Systém událostí (events) a automatické překreslení okna
Velmi důležitým konceptem, s nímž se v knihovně Pyglet setkáme, je systém událostí (events). Při běhu aplikace totiž dochází ke vzniku událostí, které jsou vyvolány jak samotným systémem (časovač...), tak i uživatelem. Na tyto události může aplikace reagovat s použitím takzvaných callback funkcí. Ty nemusí být registrovány takovým způsobem, jako je tomu v mnoha jiných knihovnách (explicitní registrací), ale musí být označeny vhodným dekorátorem. Příkladem může být callback funkce zavolaná ve chvíli, kdy je zapotřebí překreslit okno aplikace. Dekorátor pro takovou funkci se jmenuje window.event, takže příslušná callback funkce může vypadat následovně:
@window.event
def on_draw():
pass
Interně systém událostí pracuje přibližně takto:
- Knihovna Pyglet si při své inicializaci vytvoří tabulku callback funkcí na základě analýzy dekorátorů funkcí
- Po spuštění aplikace se spustí nekonečná smyčka nazvaná event loop, která postupně z takzvané fronty událostí vybírá jednotlivé události (samozřejmě jen za předpokladu, že k nim došlo)
- Následně se pro událost získanou z fronty zavolá příslušné callback funkce
Ukažme si demonstrační příklad, v němž je taková callback funkce skutečně vytvořena. Jedinou činností vyvolanou callback funkcí je smazání okna aplikace:
#!/usr/bin/env python
import pyglet
window = pyglet.window.Window(width=640,
height=480,
caption="Pyglet library")
@window.event
def on_draw():
window.clear()
pyglet.app.run()
Obrázek 5: Prázdné okno vykreslené druhým demonstračním příkladem (totožný obsah s příkladem prvním i druhým).
6. Vykreslení textového návěští
Předchozí demonstrační příklady po svém spuštění zobrazily pouze prázdné černé okno, což zajisté není příliš imponující. Zkusme si tedy do tohoto okna něco vykreslit. Jednou z důležitých vlastností knihovny Pyglet je velmi dobrá podpora pro práci s textem, resp. přesněji řečeno podpora pro vykreslování textu do okna (či na celou obrazovku). Pro vykreslení textu se používá objekt typu Label, který vytvoříme zavoláním pyglet.text.Label(). Této funkci/konstruktoru se předá především vlastní text, který se má vykreslit a následně přes nepovinné pojmenované parametry i další vlastnosti textu, zejména použitý font (libovolný TrueType font), velikost fontu a taktéž umístění textu na obrazovce. Při určování souřadnic textového návěští je dobré mít na paměti, že vertikální souřadnice rostou směrem nahoru. Následující příklad umístí začátek textového návěští doprostřed okna:
#!/usr/bin/env python
import pyglet
window = pyglet.window.Window(width=640,
height=480,
caption="Pyglet library")
label = pyglet.text.Label("Pyglet library",
font_name="Terminus",
font_size=36,
x=window.width//2,
y=window.height//2)
@window.event
def on_draw():
window.clear()
label.draw()
pyglet.app.run()
Obrázek 6: Screenshot získaný po spuštění tohoto demonstračního příkladu.
7. Specifikace úchytů textových návěští
V předchozím příkladu ležely počáteční souřadnice textového návěští v jeho levém spodním rohu, tj. přibližně v oblasti, kde začínalo písmeno „P“. To je poněkud nepříjemné, jelikož například u nadpisů je lepší umístění na zvolený střed atd. I tuto funkcionalitu nám knihovna Pyglet zajišťuje, protože pomocí nepovinných parametrů anchor_x a anchor_y lze zvolit, který z důležitých bodů textového návěští bude považován za záchytný bod – volit je možné rohy pomyslného obdélníka, v němž se návěští nachází nebo středy stran tohoto obdélníka. Ostatně podívejme se na další příklad:
#!/usr/bin/env python
import pyglet
window = pyglet.window.Window(width=640,
height=480,
caption="Pyglet library")
label1 = pyglet.text.Label("Pyglet library",
font_name="Terminus",
font_size=36,
x=window.width//2,
y=window.height//2,
anchor_x='center',
anchor_y='center')
label2 = pyglet.text.Label("Pyglet library",
font_name="Terminus",
font_size=36,
x=0,
y=window.height,
anchor_x='left',
anchor_y='top')
label3 = pyglet.text.Label("Pyglet library",
font_name="Terminus",
font_size=36,
x=window.width,
y=0,
anchor_x='right',
anchor_y='bottom')
@window.event
def on_draw():
window.clear()
label1.draw()
label2.draw()
label3.draw()
pyglet.app.run()
Obrázek 7: Screenshot získaný po spuštění tohoto demonstračního příkladu.
8. Volání funkcí OpenGL přes rozhraní knihovny Pyglet
Nyní se – prozatím bez podrobnějšího popisu – podívejme na další příklad, v němž je vykreslen bílý trojúhelník. Pro vykreslení trojúhelníku se používá knihovna OpenGL, přičemž specifikace vrcholů trojúhelníka je uzavřena mezi „příkazové závorky“:
pyglet.gl.glBegin(pyglet.gl.GL_TRIANGLES)
pyglet.gl.glEnd()
Jednotlivé vrcholy se specifikují příkazem pyglet.gl.glVertex2f() popř. některou další variantou tohoto příkazu popsaného příště:
pyglet.gl.glBegin(pyglet.gl.GL_TRIANGLES)
pyglet.gl.glVertex2f(0,0)
pyglet.gl.glVertex2f(window.width,0)
pyglet.gl.glVertex2f(window.width,window.height)
pyglet.gl.glEnd()
Úplný zdrojový kód tohoto příkladu umisťuje vykreslování do callback funkce on_draw():
#!/usr/bin/env python
import pyglet
import pyglet.gl
window = pyglet.window.Window(width=640,
height=480,
caption="Pyglet+OpenGL")
@window.event
def on_draw():
pyglet.gl.glClear(pyglet.gl.GL_COLOR_BUFFER_BIT)
pyglet.gl.glLoadIdentity()
pyglet.gl.glBegin(pyglet.gl.GL_TRIANGLES)
pyglet.gl.glVertex2f(0,0)
pyglet.gl.glVertex2f(window.width,0)
pyglet.gl.glVertex2f(window.width,window.height)
pyglet.gl.glEnd()
pyglet.app.run()
Obrázek 8: Screenshot získaný po spuštění tohoto demonstračního příkladu.
9. Zjednodušení volání funkcí OpenGL
Předchozí příklad je možné zjednodušit a přiblížit tak jeho kód, který se do značné míry bude podobat podobně koncipovanému kódu napsanému v céčku. Namísto:
import pyglet.gl
totiž použijeme alternativní import všech funkcí OpenGL do aktuálního jmenného prostoru:
from pyglet.gl import *
Zdrojový kód tohoto příkladu se nám tedy zjednoduší:
#!/usr/bin/env python
import pyglet
from pyglet.gl import *
window = pyglet.window.Window(width=640,
height=480,
caption="Pyglet+OpenGL")
@window.event
def on_draw():
glClear(GL_COLOR_BUFFER_BIT)
glLoadIdentity()
glBegin(GL_TRIANGLES)
glVertex2f(0,0)
glVertex2f(window.width,0)
glVertex2f(window.width,window.height)
glEnd()
pyglet.app.run()
10. Základní vlastnosti grafických primitiv v OpenGL
Před ukázáním dalších možností OpenGL si musíme vysvětlit, s jakými grafickými objekty se pracuje a jaké vlastnosti tyto objekty mají. V knihovně OpenGL lze pro vykreslování plošných i prostorových scén použít deset základních geometrických tvarů, které se někdy nazývají (grafická) primitiva. Těchto deset typů primitiv je možné z hlediska jejich vlastností rozdělit do tří skupin:
- body: pouze jeden typ primitiva zadaného příkazem glBegin(GL_POINTS)
- úsečky: (nemají plochu, tudíž se nevyplňují) primitiva zadaná příkazy glBegin(GL_LINES), glBegin(GL_LINE_STRIP) a glBegin(GL_LINE_LOOP)
- plošné útvary: primitiva zadaná příkazy glBegin(GL_TRIANGLES), glBegin(GL_TRIANGLE_FAN), glBegin(GL_TRIANGLE_STRIP), glBegin(GL_QUADS), glBegin(GL_QUAD_STRIP) a glBegin(GL_POLYGON)
Příklady grafickým primitiv jsou ukázány na následujících obrázcích:
Obrázek 9: Grafické primitivum GL_POINTS.
Obrázek 10: Grafické primitivum GL_LINES.
Obrázek 11: Grafické primitivum GL_LINE_STRIP.
Obrázek 12: Grafické primitivum GL_LINE_LOOP.
Obrázek 13: Grafické primitivum GL_TRIANGLES.
Obrázek 14: Grafické primitivum GL_TRIANGLE_FAN.
Obrázek 15: Grafické primitivum GL_TRIANGLE_STRIP.
Obrázek 16: Grafické primitivum GL_QUADS.
Obrázek 17: Grafické primitivum GL_QUAD_STRIP.
Obrázek 18: Grafické primitivum GL_POLYGON.
11. Vlastnosti plošných útvarů (polygonů), včetně trojúhelníků
U plošných útvarů lze, ostatně podobně jako u bodů a úseček, měnit jejich barvu. Pokud se barva specifikuje před příkazem glBegin(), je celý útvar vykreslen jednou konstantní barvou (pokud nepoužijeme texturování a osvětlení – viz další díly tohoto seriálu). Pokud však barvu specifikujeme mezi „závorkovými“ příkazy glBegin() a glEnd(), lze měnit barvu jednotlivých vrcholů. Grafická karta nebo softwarová implementace OpenGL potom provede interpolaci barvy. Tímto jednoduchým způsobem je možné v OpenGL dosáhnout Gouraudova stínování.
Každý plošný útvar má dvě strany (faces) – rub a líc (back a front). Tyto strany (orientace se zjistí z normálového vektoru) mohou být v případě potřeby vykresleny odlišně. Toho se dá využít například při řezu objektů, kdy je jasné, která strana leží uvnitř a která vně tělesa. Odvrácené strany úplně uzavřených těles lze ořezat (culling).
Pro rub a/nebo líc je možné zadat mód vykreslování. Jsou možné tři základní varianty:
- vykreslí se pouze vrcholy polygonu (plošného útvaru)
- vykreslí se hrany polygonu
- vykreslí se vyplněný polygon
Mód vykreslování se změní příkazem glPolygonMode(face, mode), kde parametr face může nabývat hodnot GL_FRONT_AND_BACK (obě strany), GL_FRONT (přední strana) a GL_BACK (zadní strana). Parametr mode může nabývat hodnot GL_POINT (vykreslují se vrcholy), GL_LINE (vykreslují se hrany) a GL_FILL (vyplněný polygon).
U polygonů lze určit, jakým způsobem jsou zadány přední a zadní strany. Ty se vždy určují podle orientace vrcholů polygonu. Způsob určování se zadává příkazem glFrontFace(GLenum mode), kde parametr mode nabývá hodnot GL_CCW (vrcholy jsou orientovány proti směru hodinových ručiček, což je implicitní nastavení) nebo GL_CW (vrcholy jsou orientovány po směru hodinových ručiček). Lze také určit, které polygony mají být ze scény odstraněny (již zmíněný culling) ještě před jejich vykreslením. Výběr se provede příkazem glCullFace(GLenum mode), kde parametr mode nabývá hodnot GL_FRONT_AND_BACK (obě strany), GL_FRONT (přední strana) nebo GL_BACK (zadní strana).
Poslední vlastností plošných útvarů je výplňový vzorek. V případě, že je plošný útvar zobrazený s vykreslovacím módem GL_FILL, je možné zadat bitovou masku o velikosti 32×32 bitů, která určuje vzorek, jímž se polygon vyplní. Vzorek se dlaždicovitě opakuje, dokud nevyplní celou plochu polygonu. Nulový bit označuje pixel, kam se nevykresluje, bit nastavený na jedničku označuje pixel, kam se vykreslování provede. Výplňový vzorek se změní příkazem glPolygonStipple(mask), ovšem v praxi se s tímto příkazem již prakticky nesetkáme, protože se namísto bitových vzorků používá plnohodnotné texturování.
12. Barevný trojúhelník aneb změna stavu OpenGL
Vzhledem k tomu, že již víme, že OpenGL je modifikovatelný stavový stroj, je možné mj. měnit barvy jednotlivých vrcholů trojúhelníku. V tomto případě grafický akcelerátor trojúhelník vyplní s využitím takzvaného Gouraudova stínování, tj. plynulým přechodem mezi jednotlivými nastavenými barvami. Barvu každého vrcholu je nutné specifikovat před nastavením jeho souřadnic funkcí glVertex2f() (nebo jakoukoli jinou variantou funkce glVertex*). Zdrojový kód příkladu vykreslujícího trojúhelník, v němž má každý vrchol odlišnou barvu, vypadá následovně:
#!/usr/bin/env python
import pyglet
from pyglet.gl import *
window = pyglet.window.Window(width=640,
height=480,
caption="Pyglet+OpenGL")
@window.event
def on_draw():
glClear(GL_COLOR_BUFFER_BIT)
glLoadIdentity()
glBegin(GL_TRIANGLES)
glColor3f(1, 0, 0)
glVertex2f(window.width/2, 0)
glColor3f(0, 1, 0)
glVertex2f(0, window.height)
glColor3f(0, 0, 1)
glVertex2f(window.width, window.height)
glEnd()
pyglet.app.run()
Obrázek 19: Výstup dalšího demonstračního příkladu s RGB trojúhelníkem.
13. Repositář s demonstračními příklady
Všechny dnes popisované demonstrační příklady byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/presentations. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý repositář:
Příklad | Odkaz |
---|---|
01_empty_window.py | https://github.com/tisnik/presentations/blob/master/pyglet/01_empty_window.py |
02_empty_window_script.py | https://github.com/tisnik/presentations/blob/master/pyglet/02_empty_window_script.py |
03_on_draw_event.py | https://github.com/tisnik/presentations/blob/master/pyglet/03_on_draw_event.py |
04_label.py | https://github.com/tisnik/presentations/blob/master/pyglet/04_label.py |
05_label_anchors.py | https://github.com/tisnik/presentations/blob/master/pyglet/05_label_anchors.py |
06_opengl.py | https://github.com/tisnik/presentations/blob/master/pyglet/06_opengl.py |
07_opengl2.py | https://github.com/tisnik/presentations/blob/master/pyglet/07_opengl2.py |
08_triangle.py | https://github.com/tisnik/presentations/blob/master/pyglet/08_triangle.py |
14. Odkazy na Internetu
- Pyglet Home Page
https://bitbucket.org/pyglet/pyglet/wiki/Home - Dokumentace k verzi 1.2
https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/ - Dokumentace k verzi 1.2 ve formátu PDF
https://readthedocs.org/projects/pyglet/downloads/pdf/pyglet-1.2-maintenance/ - PyOpenGL
http://pyopengl.sourceforge.net/ - The #! magic, details about the shebang/hash-bang mechanism on various Unix flavours
https://www.in-ulm.de/~mascheck/various/shebang/ - Shebang (Unix)
https://en.wikipedia.org/wiki/Shebang_%28Unix%29 - Domovská stránka systému LÖVE
http://love2d.org/ - Simple DirectMedia Layer (home page)
http://www.libsdl.org/ - Simple DirectMedia Layer (Wikipedia)
https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer - Seriál Grafická knihovna OpenGL
https://www.root.cz/serialy/graficka-knihovna-opengl/ - Pyglet event loop
http://pyglet.readthedocs.io/en/latest/programming_guide/eventloop.html - Decorators I: Introduction to Python Decorators
http://www.artima.com/weblogs/viewpost.jsp?thread=240808