V dnešní části seriálu o multimediální knihovně Pyglet si nejprve řekneme, jaké potenciální výkonnostní problémy mohou nastat při použití režimu přímého vykreslování (immediate mode), který jsme až doposud v demonstračních příkladech využívali. Posléze si ukážeme jedno z velmi elegantních řešení těchto problémů. Toto řešení spočívá v použití takzvaných vertex listů nabízených knihovnou Pyglet.

Obsah

1. Režim přímého vykreslování (immediate mode) v knihovně OpenGL

2. Nevýhody přímého režimu při použití na moderních grafických akcelerátorech

3. Techniky, které dokážou vyřešit některé nevýhody přímého režimu

4. Display listy

5. Knihovna Pyglet a podpora takzvaných vertex listů

6. Jednoduchý vertex list se souřadnicemi vrcholů ve 2D rovině či v 3D prostoru

7. První demonstrační příklad – vykreslení 2D scény funkcí pyglet.graphics.draw

8. Konstruktor pyglet.graphics.vertex_list a metoda pyglet.graphics.vertexdomain.VertexList.draw

9. Druhý demonstrační příklad – vykreslení 2D scény metodou pyglet.graphics.vertexdomain.VertexList.draw

10. Specifikace formátu dat uložených do vertex listu

11. Kombinace více typů informací o vrcholech (souřadnice, barvy, texturovací souřadnice...)

12. Třetí demonstrační příklad – vertex list se souřadnicemi a současně i barvami vrcholů

13. Interní struktura objektu typu pyglet.graphics.vertexdomain.VertexList

14. Čtvrtý demonstrační příklad – výpis atributů vytvořeného vertex listu

15. Pátý demonstrační příklad – složitější vertex list s více údaji o vrcholech

16. Repositář s demonstračními příklady

17. Odkazy na Internetu

1. Režim přímého vykreslování (immediate mode) v knihovně OpenGL

Ve všech předchozích částech seriálu o multimediální knihovně Pyglet, v nichž jsme vykreslovali dvourozměrné či trojrozměrné scény s použitím funkcí grafické knihovny OpenGL, se využíval takzvaný režim přímého vykreslování neboli immediate mode. Tento režim je specifický tím, že se v něm používají „příkazové závorky“ představované funkcemi glBegin() a glEnd(). U funkce glBegin() se při jejím volání specifikuje typ grafického primitiva (body, úsečky, polyčáry, uzavřené polyčáry, trojúhelníky, trsy trojúhelníků, pruhy trojúhelníků atd.) a mezi glBegin() a glEnd() je možné zadávat libovolné množství souřadnic vrcholů v 2D rovině či 3D prostoru, barev vrcholů, sekundárních barev vrcholů, souřadnic v prostoru textur (texture space), příznaky zobrazení hran atd.

Přímý režim vykreslování přináší programátorům jednu podstatnou výhodu, kterou nelze opomíjet – jeho použití je jednoduché a snadno pochopitelné. To mj. znamená, že je k dispozici relativně velké množství návodů, jak přímý režim použít. Dále se při použití přímého režimu nemusí předem alokovat žádná paměť na grafickém akcelerátoru, což opět zjednodušuje výsledný zdrojový kód.

Jen pro připomenutí si ukažme, jak vypadá část programu používající přímý režim. Povšimněte si již zmíněných „příkazových závorek“ glBegin() a glEnd():

def draw_walls():
    glBegin(GL_QUADS)                         # vykresleni otevrene krychle - sten domecku
    glColor3f(0.0, 0.0, 1.0)                  # modra barva steny
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 5.0, -5.0,  5.0)
    glTexCoord2f(0.0, 0.7)
    glVertex3f( 5.0, -5.0, -5.0)

    glColor3f(0.0, 1.0, 0.0)                  # zelena barva steny
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 5.0,  5.0,  5.0)
    glTexCoord2f(0.0, 0.7)
    glVertex3f( 5.0,  5.0, -5.0)

    glColor3f(1.0, 0.0, 0.0)                  # cervena barva steny
    glTexCoord2f(0.0, 0.0)
    glVertex3f(-5.0, -5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f(-5.0, -5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f(-5.0,  5.0,  5.0)
    glTexCoord2f(0.0, 0.7)
    glVertex3f(-5.0,  5.0, -5.0)

    glColor3f(1.0, 1.0, 0.0)                  # zluta barva steny
    glTexCoord2f(0.0, 0.0)
    glVertex3f( 5.0, -5.0, -5.0)
    glTexCoord2f(0.7, 0.0)
    glVertex3f( 5.0, -5.0,  5.0)
    glTexCoord2f(0.7, 0.7)
    glVertex3f( 5.0,  5.0,  5.0)
    glTexCoord2f(0.0, 0.7)
    glVertex3f( 5.0,  5.0, -5.0)
    glEnd()

2. Nevýhody přímého režimu při použití na moderních grafických akcelerátorech

Přímý režim má však také několik poměrně závažných nevýhod, které nakonec vedly k tomu, že se s jeho použitím v mnoha moderních aplikacích (typicky v počítačových hrách) vůbec nesetkáme. Tyto nevýhody dokonce způsobily, že v novějších verzích OpenGL již není přímý režim oficiálně podporován, což je na jednu stranu škoda (začínající programátoři to mají mnohem těžší), na druhou stranu je však vývoj OpenGL pochopitelně diktován zájmy těch firem, které za rozvojem této knihovny stojí. Jaké jsou tedy největší nevýhody přímého režimu?

  • Pro každý snímek vykreslované trojrozměrné scény je nutné přenášet (mnohdy ta stejná) data neustále znovu, což je z pohledu moderních grafických akcelerátorů velmi pomalá a navíc neefektivní operace, která navíc může zdržovat i další činnosti počítače (přenos dat na disk, komunikace po síti atd.).
  • Grafický akcelerátor může začít vykreslovat stěny těles až ve chvíli, kdy jsou do jeho paměti přenesena všechna potřebná data. Nicméně z hlediska architektury aplikace je vykreslování zdrženo, protože typicky se v OpenGL provádí operace v tomto pořadí: prohoď buffery → vymaž zadní buffer → teprve poté začni vykreslovat. To přináší zbytečné zdržení.
  • Navíc je nutné podotknout, že data zapisovaná „přímo“ funkcemi typu glVertex*, glTexCoord* či glColor* musí být nejdříve zpracována mikroprocesorem, serializována a teprve poté přenesena do grafického akcelerátoru. Při přenosu se používají různé buffery, opět se jedná o mnohdy zbytečnou komplikaci.
  • Přímý režim typicky vyžaduje klasickou „pevnou“ vykreslovací pipeline, zatímco moderní GPU jsou plně programovatelné, takže – velmi stručně a nepřesně řečeno – vyžadují na vstupu jen datový tok se souřadnicemi popř. barvami, nikoli složitější strukturované údaje.

3. Techniky, které dokážou vyřešit některé nevýhody přímého režimu

Volání každé funkce s sebou nese poměrně značné množství práce pro mikroprocesor, zejména přesun dat mezi registry procesoru, pamětí a zásobníkem a také skok do funkce a návrat z funkce (snížení účinnosti cache paměti z hlediska lokálnosti odkazů a přepínání mezi ringy). Proto by bylo vhodné co nejvíce snížit množství volání funkcí nutných pro vykreslení dané scény, ideálně použít například jen jediné volání funkce pro vykreslení každého tělesa, nezávisle na jeho složitosti. Existuje více možností vedoucích ke snížení počtu volaných funkcí, které se liší svou složitostí, vyjadřovací schopností (tj. které grafické efekty lze, popř. naopak nelze vytvořit) a rychlostí vlastního vykreslení:

Historicky nejstarší možností je použití takzvaných display listů, kdy se příkazy nutné pro vytvoření tělesa zapíšou do display listu a ten se poté zavolá pomocí jediné funkce. Výhodou tohoto postupu je, že data uložená v display listu jsou v ideálním případě zapsána přímo do paměti grafického akcelerátoru (záleží však na konkrétních podmínkách) a při každém vykreslení tedy není nutné tato data přenášet po sběrnici, což je v dnešní době (se současnými sběrnicemi a grafickými akcelerátory) nejvíce zpomalující operace (bottle neck) při vykreslování trojrozměrných scén.

Další možností je použití pole vrcholů (vertex arrays), kdy jsou data pro jednotlivé vrcholy uložena ve vhodně organizovaném poli a následně je umožněno poslání dat vrcholů pomocí několika málo funkčních volání. Výhodou tohoto přístupu je, že se mezi jednotlivými snímky mohou měnit data jednotlivých vrcholů (barva, poloha apod.), což v případě display listů nebylo možné, protože by se při jakékoliv změně musel vytvořit nový display list.

Z původních polí vrcholů se postupně vyvinuly takzvané Vertex Buffer Object mnohdy zkracované na VBO. Pole vrcholů jsou alokována v paměti grafického akcelerátoru, takže data v nich uložená jsou dostupná ještě před začátkem vykreslování celé scény, což je velmi důležité, neboť se celý proces vykreslování významným způsobem urychluje. V současnosti představují VBO prakticky nejrychlejší možnost, kterou nám klasické 3D akcelerátory nabízí. Navíc je možné data zpracovávat konfigurovatelnými shadery, což nebylo u přímého režimu možné.

4. Display listy

Display-listy si můžeme představit jako makra, do kterých se nahraje několik příkazů OpenGL, a tato makra lze potom jedním příkazem „spustit“. Výhodou display-listů je na jedné straně zvýšená rychlost vykreslování, protože display-listy jsou většinou uloženy přímo v paměti grafického akcelerátoru, na straně druhé také zjednodušení kódu pro vykreslování komplikovaných scén. Je například možné 3D modely jednotlivých těles ukládat do samostatných display-listů a složitou scénu potom vykreslit pouze zavoláním těchto display listů s vhodně nastavenou transformační maticí.

Začátek záznamu do display-listu se v grafické knihovně OpenGL povolí příkazem glNewList(), ukončení záznamu příkazem glEndList(). V příkazu glNewList(list, mode) zadáváme dva parametry. První celočíselný parametr list představuje identifikátor vytvořeného display-listu. Pomocí tohoto identifikátoru můžeme display-list vyvolat. Druhý parametr mode určuje, zda se má display-list pouze vytvořit (hodnota GL_COMPILE), nebo vytvořit a přímo provést (hodnota GL_COMPILE_AND_EXECUTE). Většina volání příkazů OpenGL (jsou však výjimky) je do display-listu zaznamenána, ostatní příkazy se samozřejmě provedou.

Provedení display-listu (tj. vyvolání příkazů uložených v display-listu) zajistíme funkcí glCallList(list), v jejímž parametru list je uložen identifikátor dříve vytvořeného display-listu.

5. Knihovna Pyglet a podpora takzvaných vertex listů

Při použití multimediální knihovny Pyglet se nemusíme zabývat tím, jak bude vykreslování provedeno na nižší úrovni (tedy na úrovni volání funkcí OpenGL), protože Pyglet programátorům nabízí velmi užitečnou datovou abstrakci nazvanou příznačně vertex list. Tento název poměrně dobře odpovídá tomu, jak jsou vertex listy konstruovány – programátor musí při jejich vytváření (konstrukci) přesně specifikovat formát zapisovaných údajů, tj. které atributy jednotlivých vertexů (vrcholů) budou explicitně specifikovány a jaký budou mít formát. Mezi podporované atributy patří zejména souřadnice ve 2D ploše či v 3D prostoru, texturovací souřadnice, barvy vrcholů a normálové vektory vrcholů. Následně se vykreslení celého vertex listu (ten může představovat například samostatné trojrozměrné těleso ve scéně) provede jediným příkazem. Specifikace dat pro vertex list je velmi flexibilní a poměrně jednoduchá, zejména v porovnání s VBO vytvářenými v jazycích typu C či C++. Některé možnosti vertex listů si představíme v demonstračních příkladech popsaných v navazujících kapitolách.

6. Jednoduchý vertex list se souřadnicemi vrcholů ve 2D rovině či v 3D prostoru

Podívejme se, jak může být reprezentován jednoduchý vertex list s osmicí vrcholů ležících ve 2D rovině. Každý vrchol je v tomto případě reprezentován dvojicí číselných hodnot. Typicky se jedná o proměnné typu float. V takovém případě bude typ dat ve vertex listu reprezentován řetězcem „v2f“, kde písmeno v značí vertex, hodnota 2 počet čísel pro vertex (x a y) a konečně písmeno f značí float. Celý vertex list lze reprezentovat jako n-tici (konkrétně dvojici), jejímž prvním prvkem je řetězec s formátem a druhým prvkem n-tice se souřadnicemi:

('v2f', (-0.5, -0.5,
         -0.5, +0.5,
         +0.5, +0.5,
         +0.5, -0.5,
         +0.2, +0.2,
         -0.2, -0.2,
         -0.2, +0.2,
         +0.2, -0.2))

Pro vertex list s vrcholy v 3D prostoru postačuje nepatrná úprava:

('v3f', (-0.5, -0.5, 0.0,
         -0.5, +0.5, 0.0,
         +0.5, +0.5, 0.0,
         +0.5, -0.5, 0.0,
         +0.2, +0.2, 10.0,
         -0.2, -0.2, 10.0,
         -0.2, +0.2, 10.0,
         +0.2, -0.2, 10.0))

7. První demonstrační příklad – vykreslení 2D scény funkcí pyglet.graphics.draw

V dnešním prvním demonstračním příkladu je nejdříve vytvořen vertex list:

def prepare_scene():
    # seznamu vertexu
    # parametry:
    #     v2f - format: vertex se dvema souradnicemi typu 'float'
    #     ()  - n-tice s osmi vertexy (8x2 = 16 hodnot typu 'float')
    return ('v2f', (-0.5, -0.5,
                    -0.5, +0.5,
                    +0.5, +0.5,
                    +0.5, -0.5,
                    +0.2, +0.2,
                    -0.2, -0.2,
                    -0.2, +0.2,
                    +0.2, -0.2))

Obrázek 1: První demonstrační příklad používající entity GL_LINES.

Ten je následně vykreslen, a to konkrétně funkcí pyglet.graphics.draw, které se předá jak samotný vertex list, tak i počet vrcholů a typ grafické entity, která se má vykreslit (v samotné vertex listu tato informace není uložena):

def draw_scene():
    global vertex_list
    # prime vykresleni ctyr usecek, kazda je reprezentovana dvojici vertexu
    pyglet.graphics.draw(8, GL_LINES, vertex_list)

Připomeňme si, že při vykreslování úseček (GL_LINES) je nutné pro každou úsečku specifikovat dva vrcholy (vertexy), což znamená, že se v našem konkrétním případě vykreslí jen čtyři úsečky.

Obrázek 2: První demonstrační příklad používající entity GL_LINE_STRIP.

Úplný zdrojový kód tohoto příkladu vypadá následovně:

#!/usr/bin/env python

import pyglet
from pyglet.gl import *
from pyglet.window import key

nearPlane = 0.1                            # blizsi orezavaci rovina
farPlane = 90.0                            # vzdalenejsi orezavaci rovina

window = pyglet.window.Window(width=500,
                              height=500,
                              caption="Pyglet library")

keys = key.KeyStateHandler()
window.push_handlers(keys)


def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)       # barva pozadi obrazku


@window.event
def on_resize(width, height):
    init()
    glViewport(0, 0, width, height)        # viditelna oblast pres cele okno


def prepare_scene():
    # seznamu vertexu
    # parametry:
    #     v2f - format: vertex se dvema souradnicemi typu 'float'
    #     ()  - n-tice s osmi vertexy (8x2 = 16 hodnot typu 'float')
    return ('v2f', (-0.5, -0.5,
                    -0.5, +0.5,
                    +0.5, +0.5,
                    +0.5, -0.5,
                    +0.2, +0.2,
                    -0.2, -0.2,
                    -0.2, +0.2,
                    +0.2, -0.2))


def draw_scene():
    global vertex_list
    # prime vykresleni ctyr usecek, kazda je reprezentovana dvojici vertexu
    pyglet.graphics.draw(8, GL_LINES, vertex_list)


def clear_buffers():
    # vymazani vsech bitovych rovin barvoveho bufferu
    glClear(GL_COLOR_BUFFER_BIT)


def set_projection_matrix(nearPlane, farPlane):
    # zacatek modifikace projekcni matice
    glMatrixMode(GL_PROJECTION)
    # vymazani projekcni matice (=identita)
    glLoadIdentity()


def set_modelview_matrix():
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()                       # nahrat jednotkovou matici


@window.event
def on_draw():
    clear_buffers()
    set_projection_matrix(nearPlane, farPlane)
    set_modelview_matrix()
    draw_scene()                    # vykresleni sceny


vertex_list = prepare_scene()
pyglet.app.run()

Obrázek 3: První demonstrační příklad používající entity GL_LINE_LOOP.

8. Konstruktor pyglet.graphics.vertex_list a metoda pyglet.graphics.vertexdomain.VertexList.draw

Předchozí demonstrační příklad sice skutečně používal vertex list, ovšem nikoli optimálním způsobem. Problém spočívá v tom, že se při každém vykreslení musí vertex list znovu zpracovávat, zatímco by bylo výhodnější provést jeho konstrukci jen jedenkrát a poté ho již jednoduše vykreslit. I tento způsob je samozřejmě v knihovně Pyglet podporovaný. Vertex list se zkonstruuje, zkonvertuje a případně i optimalizuje pomocí konstruktoru pyglet.graphics.vertex_list. Tomuto konstruktoru je nutné předat n-tici, konkrétně dvojici, přičemž v prvním prvku dvojice je uložen počet vertexů a druhý prvek obsahuje specifikaci vertex listu ve formátu, který jsme si popsali v šesté kapitole. Funkce pro vytvoření vertex listu tedy může vypadat následovně:

def prepare_scene():
    # priprava seznamu vertexu
    # parametry:
    #     8   - pocet vertexu
    #     v2f - format: vertex se dvema souradnicemi typu 'float'
    #     ()  - n-tice s osmi vertexy (8x2 = 16 hodnot typu 'float')
    return pyglet.graphics.vertex_list(8, ('v2f', (-0.5, -0.5,
                                                   -0.5, +0.5,
                                                   +0.5, +0.5,
                                                   +0.5, -0.5,
                                                   +0.2, +0.2,
                                                   -0.2, -0.2,
                                                   -0.2, +0.2,
                                                   +0.2, -0.2)))

Takto vytvořený vertex list se vykreslí metodou draw(), které se pouze předá typ vykreslované entity. V našem případě se budou vykreslovat úsečky, takže této metodě předáme konstantu GL_LINES:

def draw_scene():
    global vertex_list
    # vykresleni ctyr usecek, data o useckach jsou ulozeny ve vertex listu
    vertex_list.draw(GL_LINES)

9. Druhý demonstrační příklad – vykreslení 2D scény metodou pyglet.graphics.vertexdomain.VertexList.draw

V dnešním druhém demonstračním příkladu jsou použity obě funkce prepare_scene() a draw_scene(), které byly popsány v předchozí kapitole. Podívejme se nyní na úplný zdrojový kód tohoto příkladu:

#!/usr/bin/env python

import pyglet
from pyglet.gl import *
from pyglet.window import key

nearPlane = 0.1                            # blizsi orezavaci rovina
farPlane = 90.0                            # vzdalenejsi orezavaci rovina

window = pyglet.window.Window(width=500,
                              height=500,
                              caption="Pyglet library")

keys = key.KeyStateHandler()
window.push_handlers(keys)


def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)       # barva pozadi obrazku


@window.event
def on_resize(width, height):
    init()
    glViewport(0, 0, width, height)        # viditelna oblast pres cele okno


def prepare_scene():
    # priprava seznamu vertexu
    # parametry:
    #     8   - pocet vertexu
    #     v2f - format: vertex se dvema souradnicemi typu 'float'
    #     ()  - n-tice s osmi vertexy (8x2 = 16 hodnot typu 'float')
    return pyglet.graphics.vertex_list(8, ('v2f', (-0.5, -0.5,
                                                   -0.5, +0.5,
                                                   +0.5, +0.5,
                                                   +0.5, -0.5,
                                                   +0.2, +0.2,
                                                   -0.2, -0.2,
                                                   -0.2, +0.2,
                                                   +0.2, -0.2)))


def draw_scene():
    global vertex_list
    # vykresleni ctyr usecek, data o useckach jsou ulozeny ve vertex listu
    vertex_list.draw(GL_LINES)


def clear_buffers():
    # vymazani vsech bitovych rovin barvoveho bufferu
    glClear(GL_COLOR_BUFFER_BIT)


def set_projection_matrix(nearPlane, farPlane):
    # zacatek modifikace projekcni matice
    glMatrixMode(GL_PROJECTION)
    # vymazani projekcni matice (=identita)
    glLoadIdentity()


def set_modelview_matrix():
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()                       # nahrat jednotkovou matici


@window.event
def on_draw():
    clear_buffers()
    set_projection_matrix(nearPlane, farPlane)
    set_modelview_matrix()
    draw_scene()                    # vykresleni sceny


vertex_list = prepare_scene()
pyglet.app.run()

Obrázek 4: Druhý demonstrační příklad vykresluje stejnou scénu, jako příklad první.

10. Specifikace formátu dat uložených do vertex listu

Při konstrukci vertex listu se formát zadává řetězcem, který je typicky složen ze tří znaků:

Pozice znaku Význam
1 typ grafických údajů
2 počet datových složek
3 datový typ

Typ grafických údajů:

Znak Význam
v souřadnice vertexu (typicky 2, 3 či 4 souřadnice)
n normálový vektor (typicky 3 souřadnice)
t souřadnice v prostoře textury
c barva vertexu
s sekundární barva
e příznak přiřazený hraně (jak se bude vykreslovat)
f souřadnice použitá při výpočtu mlhy (pokud je mlha povolena)

Druhý znak jen specifikuje počet složek, takže pro 3D souřadnice zde bude uložena hodnota 3 atd.

Ve třetím znaku je specifikován datový typ každé složky grafického údaje:

Znak Význam (odpovídající typ OpenGL)
b GLbyte
B GLubyte
s GLshort
S GLushort
i GLint
I GLuint
f GLfloat
d GLdouble

11. Kombinace více typů informací o vrcholech (souřadnice, barvy, texturovací souřadnice...)

Při konstrukci vertex listu s využitím konstruktoru pyglet.graphics.vertex_list je možné v jediném vertex listu zkombinovat větší množství informací o vertexech. Pokud například budeme chtít, aby každý vrchol (koncový bod) vykreslovaných úseček mohl mít odlišnou barvu, lze vertex list vytvořit nikoli z dvojice, ale z trojice prvků. První prvek opět obsahuje počet vertexů, druhý prvek může obsahovat souřadnice vertexů (8×2 hodnoty typu float) a třetí prvek pak barvy vrcholů (8×3 hodnoty typu bajt):

(8, ('v2f', (-0.5, -0.5,
             -0.5, +0.5,
             +0.5, +0.5,
             +0.5, -0.5,
             +0.2, +0.2,
             -0.2, -0.2,
             -0.2, +0.2,
             +0.2, -0.2)),
    ('c3B', (0xff, 0x00, 0xff,
             0xff, 0xff, 0x00,
             0x00, 0x00, 0xff,
             0x00, 0xff, 0x00,
             0x00, 0xff, 0xff,
             0xff, 0x00, 0xff,
             0x00, 0xff, 0xff,
             0xff, 0xff, 0x00)),)

Korektní počet prvků je zkontrolován po spuštění a pokud například zapomeneme zadat 8x3 barvových složek R, G, B, vyvolá se (runtime) výjimka:

ValueError: Can only assign sequence of same size

12. Třetí demonstrační příklad – vertex list se souřadnicemi a současně i barvami vrcholů

Ve třetím demonstračním příkladu využijeme výše uvedený vertex list s koncovými vrcholy úseček, přičemž každý koncový bod bude mít odlišnou barvu. Při vykreslování úseček provede grafický akcelerátor interpolaci barvy, takže vznikne jednoduchý barevný gradient:

Obrázek 5: Třetí demonstrační příklad s barvovým gradientem.

Následuje výpis zdrojového kódu tohoto demonstračního příkladu:

#!/usr/bin/env python

import pyglet
from pyglet.gl import *
from pyglet.window import key

nearPlane = 0.1                            # blizsi orezavaci rovina
farPlane = 90.0                            # vzdalenejsi orezavaci rovina

window = pyglet.window.Window(width=500,
                              height=500,
                              caption="Pyglet library")

keys = key.KeyStateHandler()
window.push_handlers(keys)


def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)       # barva pozadi obrazku


@window.event
def on_resize(width, height):
    init()
    glViewport(0, 0, width, height)        # viditelna oblast pres cele okno


def prepare_scene():
    # priprava seznamu vertexu
    # parametry:
    #     8   - pocet vertexu
    #     v2f - format: vertex se dvema souradnicemi typu 'float'
    #     ()  - n-tice s osmi vertexy (8x2 = 16 hodnot typu 'float')
    #     c3b - format: barva se tremi slozkami typu 'byte'
    #     ()  - n-tice s osmi barvami (8x3 = 24 hodnot typu 'byte')
    return pyglet.graphics.vertex_list(8, ('v2f', (-0.5, -0.5,
                                                   -0.5, +0.5,
                                                   +0.5, +0.5,
                                                   +0.5, -0.5,
                                                   +0.2, +0.2,
                                                   -0.2, -0.2,
                                                   -0.2, +0.2,
                                                   +0.2, -0.2)),
                                          ('c3B', (0xff, 0x00, 0xff,
                                                   0xff, 0xff, 0x00,
                                                   0x00, 0x00, 0xff,
                                                   0x00, 0xff, 0x00,
                                                   0x00, 0xff, 0xff,
                                                   0xff, 0x00, 0xff,
                                                   0x00, 0xff, 0xff,
                                                   0xff, 0xff, 0x00)))


def draw_scene():
    global vertex_list
    # vykresleni ctyr usecek, data o useckach jsou ulozeny ve vertex listu
    vertex_list.draw(GL_LINES)


def clear_buffers():
    # vymazani vsech bitovych rovin barvoveho bufferu
    glClear(GL_COLOR_BUFFER_BIT)


def set_projection_matrix(nearPlane, farPlane):
    # zacatek modifikace projekcni matice
    glMatrixMode(GL_PROJECTION)
    # vymazani projekcni matice (=identita)
    glLoadIdentity()


def set_modelview_matrix():
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()                       # nahrat jednotkovou matici


@window.event
def on_draw():
    clear_buffers()
    set_projection_matrix(nearPlane, farPlane)
    set_modelview_matrix()
    draw_scene()                    # vykresleni sceny


vertex_list = prepare_scene()
pyglet.app.run()

13. Interní struktura objektu typu pyglet.graphics.vertexdomain.VertexList

Po své konstrukci může objekt VertexList obsahovat tyto atributy:

Atribut Význam
vertices samotné souřadnice vertexů (pole)
normals pole normálových vektorů
tex_coords pole souřadnic v prostoru textur
colors pole barev vertexů
secondary_colors pole sekundárních barev
edge_flags pole příznaků vykreslování hran
fog_coords pole souřadnic použitých při výpočtu mlhy
multi_tex_coords pole používané ve chvíli, kdy se používá větší množství textur

14. Čtvrtý demonstrační příklad – výpis atributů vytvořeného vertex listu

Ve čtvrtém demonstračním příkladu je vytvořen vertex list a následně je vypsána jeho interní struktura funkcemi popsanými v předchozí kapitole:

#!/usr/bin/env python

import pyglet


def prepare_scene():
    # priprava seznamu vertexu
    # parametry:
    #     8   - pocet vertexu
    #     v2f - format: vertex se dvema souradnicemi typu 'float'
    #     ()  - n-tice s osmi vertexy (8x2 = 16 hodnot typu 'float')
    #     c3b - format: barva se tremi slozkami typu 'byte'
    #     ()  - n-tice s osmi barvami (8x3 = 24 hodnot typu 'byte')
    return pyglet.graphics.vertex_list(8, ('v2f', (-0.5, -0.5,
                                                   -0.5, +0.5,
                                                   +0.5, +0.5,
                                                   +0.5, -0.5,
                                                   +0.2, +0.2,
                                                   -0.2, -0.2,
                                                   -0.2, +0.2,
                                                   +0.2, -0.2)),
                                          ('c3B', (0xff, 0x00, 0xff,
                                                   0xff, 0xff, 0x00,
                                                   0x00, 0x00, 0xff,
                                                   0x00, 0xff, 0x00,
                                                   0x00, 0xff, 0xff,
                                                   0xff, 0x00, 0xff,
                                                   0x00, 0xff, 0xff,
                                                   0xff, 0xff, 0x00)))


def print_array(name, array):
    print(name)
    print("   type: %s" % type(array))
    print("   length: %d" % len(array))
    print("   content:")
    for element in array:
        print("        %6.1f" % element)


vertex_list = prepare_scene()

print("Vertex list size: %d" % vertex_list.get_size())
print_array("Colors:", vertex_list.colors)
print_array("Vertices:", vertex_list.vertices)

Po spuštění příkladu by se měly na standardní výstup vypsat tyto řádky:

Vertex list size: 8
Colors:
   type: <class 'pyglet.graphics.vertexattribute.c_ubyte_Array_24'>
   length: 24
   content:
         255.0
           0.0
         255.0
         255.0
         255.0
           0.0
           0.0
           0.0
         255.0
           0.0
         255.0
           0.0
           0.0
         255.0
         255.0
         255.0
           0.0
         255.0
           0.0
         255.0
         255.0
         255.0
         255.0
           0.0
Vertices:
   type: <class 'pyglet.graphics.vertexattribute.c_float_Array_16'>
   length: 16
   content:
          -0.5
          -0.5
          -0.5
           0.5
           0.5
           0.5
           0.5
          -0.5
           0.2
           0.2
          -0.2
          -0.2
          -0.2
           0.2
           0.2
          -0.2

15. Pátý demonstrační příklad – složitější vertex list s více údaji o vrcholech

Pátý příklad se do značné míry podobá příkladu čtvrtému, ovšem s tím rozdílem, že vertex list obsahuje čtyři typy údajů – souřadnice vrcholů, texturovací souřadnice, normálový vektor každého vertexu a konečně barvu každého vertexu:

#!/usr/bin/env python

import pyglet


def prepare_scene():
    # priprava seznamu vertexu
    # parametry:
    #     8   - pocet vertexu
    #     v2i - format: vertex se dvema souradnicemi typu 'int'
    #     ()  - n-tice s osmi vertexy (8x2 = 16 hodnot typu 'int')
    #     t2f - format: souradnice v texturovacim prostoru se dvema
    #           souradnicemi typu 'float'
    #     ()  - n-tice s osmi souradnicemi (8x2 = 16 hodnot typu 'float')
    #     n3f - format: normalovy vektor se slozkami typu 'float'
    #     ()  - n-tice s osmi barvami (8x3 = 24 hodnot typu 'float')
    #     c3b - format: barva se tremi slozkami typu 'byte'
    #     ()  - n-tice s osmi barvami (8x3 = 24 hodnot typu 'byte')
    return pyglet.graphics.vertex_list(8, ('v2i', (-50, -50,
                                                   -50, +50,
                                                   +50, +50,
                                                   +50, -50,
                                                   +20, +20,
                                                   -20, -20,
                                                   -20, +20,
                                                   +20, -20)),
                                          ('t2f', (0.0, 0.0,
                                                   1.0, 0.0,
                                                   0.0, 1.0,
                                                   1.0, 1.0,
                                                   0.0, 0.0,
                                                   1.0, 0.0,
                                                   0.0, 1.0,
                                                   1.0, 1.0)),
                                          ('n3f', (0.0, 0.0, 1.0,
                                                   1.0, 0.0, 0.0,
                                                   0.0, 1.0, 0.0,
                                                   1.0, 1.0, 0.0,
                                                   0.0, 0.0, 1.0,
                                                   1.0, 0.0, 0.0,
                                                   0.0, 1.0, 0.0,
                                                   1.0, 1.0, 0.0)),
                                          ('c3B', (0xff, 0x00, 0xff,
                                                   0xff, 0xff, 0x00,
                                                   0x00, 0x00, 0xff,
                                                   0x00, 0xff, 0x00,
                                                   0x00, 0xff, 0xff,
                                                   0xff, 0x00, 0xff,
                                                   0x00, 0xff, 0xff,
                                                   0xff, 0xff, 0x00)))


def print_array(name, array):
    print(name)
    print("   type: %s" % type(array))
    print("   length: %d" % len(array))
    print("   content:")
    for element in array:
        print("        %6.1f" % element)


vertex_list = prepare_scene()

print("Vertex list size: %d" % vertex_list.get_size())
print_array("Colors:", vertex_list.colors)
print_array("Vertices:", vertex_list.vertices)
print_array("Tex.cords:", vertex_list.tex_coords)
print_array("Normals:", vertex_list.normals)

Po spuštění příkladu by se měly na standardní výstup vypsat tyto řádky:

Vertex list size: 8
Colors:
   type: <class 'pyglet.graphics.vertexattribute.c_ubyte_Array_24'>
   length: 24
   content:
         255.0
           0.0
         255.0
         255.0
         255.0
           0.0
           0.0
           0.0
         255.0
           0.0
         255.0
           0.0
           0.0
         255.0
         255.0
         255.0
           0.0
         255.0
           0.0
         255.0
         255.0
         255.0
         255.0
           0.0
Vertices:
   type: <class 'pyglet.graphics.vertexattribute.c_int_Array_16'>
   length: 16
   content:
         -50.0
         -50.0
         -50.0
          50.0
          50.0
          50.0
          50.0
         -50.0
          20.0
          20.0
         -20.0
         -20.0
         -20.0
          20.0
          20.0
         -20.0
Tex.cords:
   type: <class 'pyglet.graphics.vertexattribute.c_float_Array_16'>
   length: 16
   content:
           0.0
           0.0
           1.0
           0.0
           0.0
           1.0
           1.0
           1.0
           0.0
           0.0
           1.0
           0.0
           0.0
           1.0
           1.0
           1.0
Normals:
   type: <class 'pyglet.graphics.vertexattribute.c_float_Array_24'>
   length: 24
   content:
           0.0
           0.0
           1.0
           1.0
           0.0
           0.0
           0.0
           1.0
           0.0
           1.0
           1.0
           0.0
           0.0
           0.0
           1.0
           1.0
           0.0
           0.0
           0.0
           1.0
           0.0
           1.0
           1.0
           0.0

16. Repositář s demonstračními příklady

Všechny dnes popsané 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ář. Pro jejich spuštění je nutné mít nainstalovanou jak knihovnu Pyglet, tak i podpůrné grafické knihovny OpenGL a GLU (což se většinou provede automaticky v rámci instalace balíčku s Pygletem, viz též úvodní díl tohoto seriálu):

Příklad Odkaz
64_draw_list_immediatelly.py https://github.com/tisnik/presentations/blob/master/pyglet/64_draw_list_immediatelly.py
65_vertex_list.py https://github.com/tisnik/presentations/blob/master/pyglet/65_vertex_list.py
66_vertex_color_list.py https://github.com/tisnik/presentations/blob/master/pyglet/66_vertex_color_list.py
67_vertex_list_attributes.py https://github.com/tisnik/presentations/blob/master/pyglet/67_vertex_list_attributes.py
68_vertex_list_attributes_2.py https://github.com/tisnik/presentations/blob/master/pyglet/68_vertex_list_attributes_2.py

17. Odkazy na Internetu

  1. Class pyglet.graphics.vertexdomain.VertexList
    https://pythonhosted.org/pyglet/api/pyglet.graphics.vertexdomain.VertexList-class.html
  2. Class pyglet.graphics.vertexdomain.VertexDomain
    https://pythonhosted.org/pyglet/api/pyglet.graphics.vertexdomain.VertexDomain-class.html
  3. Pyglet: Module Hierarchy
    https://pythonhosted.org/pyglet/api/module-tree.html
  4. Learning Modern OpenGL
    https://www.codeproject.com/articles/771225/learning-modern-opengl
  5. OpenGL Utility Library
    https://en.wikipedia.org/wiki/OpenGL_Utility_Library
  6. GLU Specification
    https://www.opengl.org/registry/doc/glu1.3.pdf
  7. The Perlin noise math FAQ
    https://mzucker.github.io/html/perlin-noise-math-faq.html
  8. Perlin noise
    https://en.wikipedia.org/wiki/Perlin_noise
  9. Perlin Noise Generator (Python recipe)
    http://code.activestate.com/recipes/578470-perlin-noise-generator/
  10. Simplex noise demystified
    http://www.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
  11. glTexEnv - příkaz OpenGL
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTexEnv.xml
  12. glGetTexEnv - příkaz OpenGL
    https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glGetTexEnv.xml
  13. Pyglet Home Page
    https://bitbucket.org/pyglet/pyglet/wiki/Home
  14. Pyglet: dokumentace k verzi 1.2
    https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/
  15. Dokumentace k verzi 1.2 ve formátu PDF
    https://readthedocs.org/projects/pyglet/downloads/pdf/pyglet-1.2-maintenance/
  16. PyOpenGL
    http://pyopengl.sourceforge.net/
  17. The #! magic, details about the shebang/hash-bang mechanism on various Unix flavours
    https://www.in-ulm.de/~mascheck/various/shebang/
  18. Shebang (Unix)
    https://en.wikipedia.org/wiki/Shebang_%28Unix%29
  19. Domovská stránka systému LÖVE
    http://love2d.org/
  20. Simple DirectMedia Layer (home page)
    http://www.libsdl.org/
  21. Simple DirectMedia Layer (Wikipedia)
    https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
  22. Seriál Grafická knihovna OpenGL
    https://www.root.cz/serialy/graficka-knihovna-opengl/
  23. Pyglet event loop
    http://pyglet.readthedocs.io/en/latest/programming_guide/eventloop.html
  24. Decorators I: Introduction to Python Decorators
    http://www.artima.com/weblogs/viewpost.jsp?thread=240808
  25. 3D Programming in Python - Part 1
    https://greendalecs.wordpress.com/2012/04/21/3d-programming-in-python-part-1/
  26. A very basic Pyglet tutorial
    http://www.natan.termitnjak.net/tutorials/pyglet_basic.html
  27. Alpha blending
    https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending