Ve čtvrté části seriálu o multimediální knihovně Pyglet se seznámíme s problematikou framebufferu používaného u grafických akcelerátorů. Následně si ukážeme, jakým způsobem je možné vykreslit trojrozměrná tělesa se správným skrýváním skrytých částí stěn. Pro tuto operaci využijeme takzvanou paměť hloubky neboli depth buffer.

Obsah

1. Zobrazení 3D scény bez použití paměti hloubky a problém překrývání stěn

2. Jedno z řešení tohoto problému – malířův algoritmus

3. Framebuffer grafických akcelerátorů

4. Typy bufferů ve framebufferu

5. Color buffer

6. Depth buffer

7. Stencil buffer

8. Accumulation buffer

9. Řešení překrývaní stěn na úrovní hardware – paměť hloubky (Z-buffer)

10. Grafické akcelerátory a použití takzvaného W-bufferu

11. Druhý demonstrační příklad: paměť hloubky prakticky

12. Blending

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

14. Odkazy na Internetu

1. Zobrazení 3D scény bez použití paměti hloubky a problém překrývání stěn

Ukažme si nejprve příklad, v němž se sice vykreslí trojrozměrná scéna, ovšem bez jakéhokoli řízení viditelnosti stěn. Jinými slovy to znamená, že každé vykreslení další stěny (zde trojúhelníku či čtyřúhelníku) povede k překreslení stěny předchozí, pokud se samozřejmě v daném místě obrázku nějaká stěna již vykreslila. Vzhledem k tomu, že vykreslovaným tělesem můžeme rotovat pomocí kurzorových šipek, lze snadno zjistit, že z některých úhlů je těleso – domeček – vykresleno korektně, ale většinou tomu tak není. Ostatně se můžeme podívat na screenshoty:

Obrázek 1: Screenshot získaný z dnešního prvního demonstračního příkladu.

Obrázek 2: Odlišná rotace tělesa (zde vše vypadá korektně).

Obrázek 3: Odlišná rotace tělesa.

Obrázek 4: Odlišná rotace tělesa.

Úplný zdrojový kód tohoto demonstračního příkladu je odvozen od příkladů, s nimiž jsme se seznámili v předchozí části tohoto seriálu:

#!/usr/bin/env python

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

fov = 70.0                                    # hodnota zorneho uhlu - field of view
nearPlane = 0.1                               # blizsi orezavaci rovina
farPlane = 90.0                               # vzdalenejsi orezavaci rovina

r1 = 0.0
r2 = 0.0

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


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



def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)          # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_FILL)          # nastaveni rezimu vykresleni vyplnenych sten
    glPolygonMode(GL_BACK, GL_FILL)           # jak pro predni tak pro zadni steny
    glDisable(GL_CULL_FACE)                   # zadne hrany ani steny se nebudou odstranovat



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



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

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

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

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



def draw_roof():
    glBegin(GL_TRIANGLES)                      # vykresleni strechy domecku z trojuhelniku
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor3f(1.0, 0.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor3f(0.0, 0.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glVertex3f( 0.0, 11.0,  0.0)
    glEnd()



@window.event
def on_draw():
    global r1, r2

    if keys[key.LEFT]:
        r2 = r2 - 1
    if keys[key.RIGHT]:
        r2 = r2 + 1
    if keys[key.UP]:
        r1 = r1 - 1
    if keys[key.DOWN]:
        r1 = r1 + 1

    glClear(GL_COLOR_BUFFER_BIT)              # vymazani vsech bitovych rovin barvoveho bufferu

    glMatrixMode(GL_PROJECTION)               # zacatek modifikace projekcni matice
    glLoadIdentity()                          # vymazani projekcni matice (=identita)
    gluPerspective(fov, 1.0, nearPlane, farPlane);

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()                          # nahrat jednotkovou matici

    gluLookAt(4.0, 6.0, 18.0,                 # bod, odkud se kamera diva
              0.0, 2.0,  0.0,                 # bod, kam se kamera diva
              0.0, 1.0,  0.0)                 # poloha "stropu" ve scene

    glRotatef(r1, 1.0, 0.0, 0.0)              # rotace objektu
    glRotatef(r2, 0.0, 1.0, 0.0) 

    draw_walls()
    draw_roof()



pyglet.app.run()

2. Jedno z řešení tohoto problému – malířův algoritmus

V případě, že se polygony rastrují do framebufferu přímo, tj. bez využití dalších programových či technických prostředků, dochází, jak již víme, k jejich nekorektnímu překryvu, protože pořadí polygonů posílané do grafického akcelerátoru obecně neodpovídá reálné viditelnosti jednotlivých stěn. V některých případech to ani není technicky možné, protože například tři polygony mohou být umístěny v prostoru takovým způsobem, že nelze rozhodnout, který je blíže k pozorovateli a který dále. Jedno z řešení viditelnosti by mohlo spočívat v ručním (algoritmickém) seřazení jednotlivých polygonů tak, že nejdříve by se vykreslily stěny (polygony) nejvzdálenější a poté by se postupně překreslovaly stěny bližší.

Toto řešení, které se také podle popsaného principu nazývá malířův algoritmus (painter’s algorithm) je však časově náročné (stěny se musí seřadit a to po každé změně v prostorové scéně nebo při každém pohybu kamery-pozorovatele), nepodporuje proudové zpracování dat (opakované řazení) a může vést k nejednoznačnostem při vyhodnocování vzdálenosti jednotlivých stěn, například v již zmíněném případě, kdy se tři stěny vzájemně překrývají. Z tohoto důvodu se v grafických akcelerátorech používá jiná technika, která je optimalizovaná pro proudové zpracování dat bez nutnosti tvorby a udržování složitých datových struktur či použití časově náročných algoritmů. V této technice je pro řešení viditelnosti jednotlivých polygonů či jejich částí (tj. fragmentů) použita takzvaná paměť hloubky, neboli depth buffer, popř. Z-buffer.

3. Framebuffer grafických akcelerátorů

V předchozí kapitole zmíněná paměť hloubky je nedílnou součástí takzvaného framebufferu, který může být implementován buď programově nebo (a to mnohem častěji) je implementován na úrovni hardwaru v GPU. Framebuffer se skládá z několika dílčích bufferů, z nichž každý je určen pro jednu nebo větší množství specifických operací, které lze při vykreslování scény provádět. Do framebufferu se zapisují nebo se z něho čtou takzvané fragmenty, což jsou – zjednodušeně řečeno – pixely, které kromě své barvy a průhlednosti obsahují i informace o hloubce (vzdálenosti od kamery), popř. i další informace. Na každý buffer se můžeme dívat jako na paměť, v níž jsou uložena rastrová data (pixely), která mají podle určení bufferu svůj specifický význam. Buffer lze vymazat nebo přečíst do hlavní paměti počítače, popř. do něj data přímo zapsat, což se používá například při programování některých speciálních grafických efektů. Představu o cestě, kterou vykonávají fragmenty před vlastním vykreslením v klasické OpenGL pipeline, nám dá následující obrázek (u moderních GPU je situace odlišná, neboť celá pipeline je programovatelná):

Obrázek 5: Klasická OpenGL pipeline.

4. Typy bufferů ve framebufferu

Při práci s OpenGL přes knihovnu Pyglet můžeme přímo či nepřímo používat čtyři typy bufferů:

  1. Color buffer(s) – barvový nebo více barvových bufferů
  2. Depth buffer – paměť hloubky, nazývaný také Z-buffer
  3. Stencil buffer – paměť se šablonou
  4. Accumulation buffer – akumulační buffer

Tyto čtyři základní typy bufferů si stručně popíšeme v navazujících kapitolách.

5. Color buffer

V barvovém bufferu (nebo bufferech) je uložena barevná informace o vykreslované scéně. Jeden z barevných bufferů je vždy zobrazen na obrazovce a představuje výsledek našeho programátorského snažení. Barvový buffer je v nejjednodušším případě pouze jeden, avšak může jich být nainicializováno několik. V barevném bufferu mohou být jednotlivé pixely uloženy buď ve formátu RGB (true-color), nebo může být použit index-color (paletový) režim, kdy pixely neobsahují přímo barvu, ale pouze index do barevné palety.

Na naprosté většině grafických akcelerátorů lze vytvořit minimálně dva barevné buffery, které lze využít při animacích k takzvanému double bufferingu, tj. k vykreslování do neviditelného bufferu a současnému zobrazení druhého bufferu. Po vykreslení snímku se úloha barevných bufferů prohodí. Podrobněji se budeme doublebufferingem zabývat již v příští části tohoto seriálu. V případě double bufferingu jsou tyto dva barevné buffery nazývány přední buffer (front buffer) a zadní buffer (back buffer). V příkazech OpenGL, které se odkazují na jednotlivé buffery, se používají symbolické konstanty GL_FRONT a GL_BACK.

Některé implementace OpenGL, které umožňují generování stereo obrázků pro různá 3D výstupní zařízení (brýle, helmy, stereo monitory apod.), mohou zobrazovat současně dva barevné buffery, jeden pro pravé a druhý pro levé oko. Tyto buffery se také podle toho jmenují levý buffer a pravý buffer. V příkazech OpenGL se používají symbolické konstanty GL_LEFT a GL_RIGHT. Pokud se kromě generování stereo obrázků používá i double buffering, existují současně čtyři barevné buffery označované jako GL_FRONT_RIGHT, GL_FRONT_LEFT, GL_BACK_RIGHT a GL_BACK_LEFT.

Kromě toho je někdy možné vytvořit i další barevné buffery, které nemají svůj specifický účel, ale lze je použít například pro různé grafické efekty. Tyto buffery se označují symbolickými konstantami GL_AUXi, kde rozsah i je určen konkrétní implementací. Množství barevných bufferů je dáno paměťovou kapacitou grafického akcelerátoru, rozlišením obrazovky, barevnou hloubkou jednotlivých bufferů a množstvím dalších bufferů (depth buffer, stencil buffer, accumulation buffer), které se vytvářejí. Každý další vytvořený buffer navíc zmenšuje volnou paměť na grafickém akcelerátoru, která se používá pro ukládání textur a pro display listy. Proto je při inicializaci aplikace vhodné povolit pouze ty buffery, které jsou skutečně zapotřebí.

6. Depth buffer

Depth buffer, tedy paměť hloubky, je někdy také nazýván Z-buffer popř. v jiné implementaci W-buffer. Jedná se o paměť, v níž jsou většinou uloženy informace zajišťující vykreslení pouze viditelných částí těles, tj. vzdálenější plošky jsou překryty ploškami bližšími. Funkci a význam paměti hloubky lze však při vykreslování průběžně měnit, takže je možné naprogramovat některé zajímavé efekty, například zobrazení stínů.

V naprosté většině případů je funkce paměti hloubky nastavena tak, že se při rasterizaci testuje Z-ová hloubka (vzdálenost od stínítka obrazovky, ve skutečnosti se většinou testuje převrácená hodnota Z-ové hloubky, ale to je již interní věcí grafického akcelerátoru) vytvořeného fragmentu s hloubkou uloženou ve framebufferu na pozici vkládaného fragmentu. Pokud je Z-ová hloubka fragmentu menší než ve framebufferu, fragment se do framebufferu uloží a samozřejmě přepíše i Z-ovou pozici. Pokud je Z-ová hloubka fragmentu větší, je jasné, že je fragment ve výsledné scéně neviditelný, a proto je zahozen.

U depth bufferu je velmi důležitá jeho bitová hloubka. Pokud je například depth buffer pouze osmibitový (tak ho měly implementovány první grafické akcelerátory na PC), je možné rozlišit pouze 256 vzdáleností, což může vést k vytvoření vizuálních artefaktů ve scéně. V dnešní době se používají 16bitové a 24bitové hloubky, kde se množství artefaktů snižuje (avšak nastat mohou, typicky pokud vytvoříme dva trojúhelníky, které se částečně překrývají).

7. Stencil buffer

Stencil buffer neboli paměť šablony je používán pro určení, do kterých míst na obrazovce je povoleno vykreslování. Jedno z možných použití stencil bufferu je při vykreslování 2D grafického uživatelského rozhraní současně s trojrozměrnou scénou. Dalším, pokročilejším použitím stencil bufferu je algoritmus pro vykreslování zrcadlících ploch nebo pro tvorbu stereo obrázků na monitoru s použitím stereo brýlí (dva pohledy na scénu snímané ze dvou bodů zobrazené prokládaně na jedné obrazovce).

U stencil bufferu lze zadat relační operaci, která se provádí mezi hodnotou fragmentu a hodnotou uloženou ve stencil bufferu. Také je možné zadat, zda a popř. při jaké podmínce se budou měnit data ve stencil bufferu. U stencil bufferu většinou není bitová hloubka tak kritická jako v případě depth bufferu. Dokonce nám pro některé jednodušší úlohy postačuje jednobitová hloubka (typicky ořezání scény do nějakého obrazce a výše zmíněná tvorba stereo obrázků).

Použitím stencil bufferu se budeme podrobněji zabývat v dalších dílech seriálu o knihovně Pyglet.

8. Accumulation buffer

Accumulation buffer obsahuje, podobně jako color buffer, rastrový obrázek. Do akumulačního bufferu je však možno akumulovat (např. sečíst) více obrázků. Akumulační buffer se používá například při programování efektu rozmazání pohybem – takzvaný motion blur. Funkce akumulačního bufferu je dostupná pouze při používání RGB (true-color) režimu (což je dnes de facto standardem). V index-color (paletových) režimech nelze akumulační buffer použít.

Poznámka: mazání bufferů je operace časově náročná, protože je nutné projít všechny pixely v každém bufferu a nastavit je na nějakou hodnotu (většinou nulu). Proto je vhodné všechny buffery mazat současně, což umožňuje příkaz glClear(mask), kde pomocí parametru mask lze specifikovat jeden nebo více bufferů, které se mají smazat. Parametr mask může být vytvořen logickým součtem hodnot: GL_COLOR_BUFFER_BIT (maže se zadní barvový buffer), GL_DEPTH_BUFFER_BIT (maže se paměť hloubky), GL_STENCIL_BUFFER_BIT (maže se paměť šablony) a GL_ACCUM_BUFFER_BIT (maže se akumulační buffer).

9. Řešení překrývaní stěn na úrovní hardware – paměť hloubky (Z-buffer)

Rozlišení Z-bufferu je většinou shodné s rozlišením výsledné rastrové mapy, která má být vykreslena. Před vykreslením nového snímku jsou hloubky všech fragmentů nastaveny na maximální vzdálenost popř. na programově zvolenou vzdálenost.V případě, že by se počáteční hloubky fragmentů nastavily na jinou hodnotu, docházelo by při vykreslování scény k ořezávání vzdálených polygonů (resp. přesněji řečeno jejich částí), což může být v některých případech žádoucí vlastnost. Na tomto místě stojí za upozornění fakt, že při vykreslování poloprůhledných polygonů, tj. polygonů s nanesenou texturou obsahující poloprůhledné texely, nedokáže Z-buffer korektně s těmito polygony pracovat. Z tohoto důvodu je nutné nejdříve (s povoleným zápisem do Z-bufferu) vykreslit všechny neprůhledné polygony a poté zakázat zápis do Z-bufferu a následně vykreslit poloprůhledné polygony, ovšem setříděné podle jejich klesající vzdálenosti od pozorovatele (kamery).

10. Grafické akcelerátory a použití takzvaného W-bufferu

Do paměti hloubky (Z-bufferu) je možné ukládat vzdálenosti fragmentů od pozorovatele (kamery) ve dvou formátech, v obou případech je však každý údaj vždy uložen v osmi, šestnácti bitech, 24 či někdy i třiceti dvou bitech. Při použití prvního způsobu se do Z-bufferu skutečně ukládají vzdálenosti fragmentů, přesněji řečeno celočíselná část vzdálenosti (výpočty vzdálenosti se provádí přesněji, ale výsledek je při ukládání zaokrouhlen). Tento formát není příliš výhodný, protože po projekci 3D scény ze světových souřadnic do prostoru obrazovky není krok mezi jednotlivými vzdálenostmi konstantní, což vede k vizuálním chybám při vykreslování (rozlišení pouze 216 vzdáleností je v tomto případě nedostatečné, lepší je to v případě 224 vzdáleností). Z tohoto důvodu se preferuje druhý způsob (nazývaný také w-buffer), při němž se do Z-bufferu ukládají převrácené hodnoty vzdálenosti, a to ve speciálním formátu čísel s pohyblivou řádovou tečkou (čárkou), který může mít například následující strukturu připomínající formát definovaný v IEEE 754 (příklad je vybrán pro šestnáctibitový formát):

1.mantissa × 2exponent

V tomto formátu je pro mantisu vyhrazeno dvanáct bitů a pro exponent pouhé čtyři bity. Povšimněte si implicitní jedničky před desetinnou tečkou i toho, že žádný bit není vyhrazen pro uložení znaménka – vzdálenosti (a samozřejmě i jejich převrácené hodnoty) jsou vždy kladné. Minimální hodnota, kterou lze tímto způsobem uložit, je rovna jedničce (0×0000 ~ 1.000000000000×20), maximální hodnota 65528.0 (0×ffff ~ 1.111111111111­×215). Podobné „krátké“ formáty čísel s plovoucí řádovou tečkou jsou v oblasti grafických akcelerátorů stále velmi oblíbené, protože práce s nimi je poměrně rychlá a požadovaná kapacita obrazové paměti je oproti plně 32bitovým formátům poloviční.

11. Druhý demonstrační příklad: paměť hloubky prakticky

Aby bylo možno paměť hloubky použít, je zapotřebí provést několik kroků:

  1. Při inicializaci knihovny OpenGL je zapotřebí naalokovat i paměť hloubky. Tuto operaci za nás provede knihovna Pyglet automaticky. Na rozdíl od barvových bufferů, kterých může být více, se naalokuje vždy jen jedna paměť hloubky, protože víc jich není zapotřebí.
  2. Funkce paměti hloubky (test hloubky fragmentů) se musí povolit příkazem glEnable(GL_DEPTH_TEST). Při vykreslování různých částí scény lze paměť hloubky nezávisle zapínat a vypínat, čehož se využívá při tvorbě různých grafických efektů, například při vykreslování průhledných těles (o této problematice jsme se již zmiňovali).
  3. S využitím funkce glClearDepth(depth) se může specifikovat, na jakou hodnotu se bude mazat paměť hloubky mezi snímky. Tato hodnota může být v rozsahu 0.0 až 1.0. Opět můžeme ponechat výchozí hodnotu.
  4. Dále je zapotřebí nastavit, jaký test se bude s hloubkou fragmentu provádět. Pro nastavení se používá funkce glDepthFunc(func), kde parametr func může nabývat hodnot GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, GL_GREATER, GL_NOTEQUAL, GL_GEQUAL a GL_ALWAYS. Pro běžnou funkci paměti hloubky se nastavuje funkce GL_LESS, tj. bližší fragment přepíše fragment vzdálenější. Pro speciální grafické efekty však lze logiku testovací funkce změnit.
  5. Posledním úkolem je nutnost mazání paměti hloubky před vykreslením každého snímku. To lze provést buď samostatně zavoláním funkce glClear(GL_DEPTH_BUFFER_BIT), nebo lze smazat současně barvový buffer i paměť hloubky zavoláním stejné funkce, ale s více bitovými příznaky: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT). Současné mazání obou bufferů je obecně rychlejší, protože na grafickém akcelerátoru je možné provést optimalizace v přístupu do paměti.

Přesně tento postup využijeme v dnešním druhém demonstračním příkladu. Povolení paměti hloubky je provedeno při inicializaci:

def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)          # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_FILL)          # nastaveni rezimu vykresleni vyplnenych sten
    glPolygonMode(GL_BACK, GL_FILL)           # jak pro predni tak pro zadni steny
    glDisable(GL_CULL_FACE)                   # zadne hrany ani steny se nebudou odstranovat
    glDepthFunc(GL_LESS)                      # funkce pro testovani fragmentu

Paměť hloubky lze povolit klávesou E (enable) a zakázat klávesou D (disable). Podle stisku těchto kláves se změní chování následujících dvou funkcí, což opět odpovídá výše popsanému postupu:

def set_depth_buffer(depthBufferEnabled):
    if depthBufferEnabled:
        glEnable(GL_DEPTH_TEST)
    else:
        glDisable(GL_DEPTH_TEST)
def clear_buffers(depthBufferEnabled):
    if depthBufferEnabled:
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # vymazani i Z/W bufferu
    else:
        glClear(GL_COLOR_BUFFER_BIT)          # vymazani vsech bitovych rovin barvoveho bufferu

Při povolení zadního bufferu získáme obrázek s korektním skrýváním stěn:

Obrázek 6: Screenshot druhého demonstračního příkladu.

Úplný zdrojový kód dnešního druhého demonstračního příkladu vypadá následovně:

#!/usr/bin/env python

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

fov = 70.0                                    # hodnota zorneho uhlu - field of view
nearPlane = 0.1                               # blizsi orezavaci rovina
farPlane = 90.0                               # vzdalenejsi orezavaci rovina

r1 = 0.0
r2 = 0.0

depthBufferEnabled = False                    # povoleni ci zakaz Z-bufferu

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


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



def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)          # barva pozadi obrazku
    glPolygonMode(GL_FRONT, GL_FILL)          # nastaveni rezimu vykresleni vyplnenych sten
    glPolygonMode(GL_BACK, GL_FILL)           # jak pro predni tak pro zadni steny
    glDisable(GL_CULL_FACE)                   # zadne hrany ani steny se nebudou odstranovat
    glDepthFunc(GL_LESS)                      # funkce pro testovani fragmentu




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



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

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

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

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



def draw_roof():
    glBegin(GL_TRIANGLES)                      # vykresleni strechy domecku z trojuhelniku
    glColor3f(0.0, 1.0, 1.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor3f(1.0, 0.0, 1.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor3f(1.0, 1.0, 1.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor3f(0.0, 0.0, 0.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glVertex3f( 0.0, 11.0,  0.0)
    glEnd()



def set_depth_buffer(depthBufferEnabled):
    if depthBufferEnabled:
        glEnable(GL_DEPTH_TEST)
    else:
        glDisable(GL_DEPTH_TEST)



def clear_buffers(depthBufferEnabled):
    if depthBufferEnabled:
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # vymazani i Z/W bufferu
    else:
        glClear(GL_COLOR_BUFFER_BIT)          # vymazani vsech bitovych rovin barvoveho bufferu



@window.event
def on_draw():
    global r1, r2
    global depthBufferEnabled

    if keys[key.LEFT]:
        r2 = r2 - 1
    if keys[key.RIGHT]:
        r2 = r2 + 1
    if keys[key.UP]:
        r1 = r1 - 1
    if keys[key.DOWN]:
        r1 = r1 + 1
    if keys[key.E]:
        depthBufferEnabled = True
    if keys[key.D]:
        depthBufferEnabled = False

    clear_buffers(depthBufferEnabled)
    set_depth_buffer(depthBufferEnabled)

    glMatrixMode(GL_PROJECTION)               # zacatek modifikace projekcni matice
    glLoadIdentity()                          # vymazani projekcni matice (=identita)
    gluPerspective(fov, 1.0, nearPlane, farPlane);

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()                          # nahrat jednotkovou matici

    gluLookAt(4.0, 6.0, 18.0,                 # bod, odkud se kamera diva
              0.0, 2.0,  0.0,                 # bod, kam se kamera diva
              0.0, 1.0,  0.0)                 # poloha "stropu" ve scene

    glRotatef(r1, 1.0, 0.0, 0.0)              # rotace objektu
    glRotatef(r2, 0.0, 1.0, 0.0) 

    draw_walls()
    draw_roof()



pyglet.app.run()

12. Blending

Grafické akcelerátory mohou provádět i takzvaný blending neboli míchání (barev). K tomuto účelu se většinou používá čtvrtá barvová složka specifikovaná příkazem glColor4f(R, G, B, A) a nikoli glColor3F(R, G, B). Při použití blendingu musíme nejdříve specifikovat, jakým způsobem se budou kombinovat právě vykreslované fragmenty s hodnotami uloženými ve framebufferu.

Připomeňme, že fragment je datová struktura složená z barvy pixelu, jeho průhlednosti, vzdálenosti od pozorovatele a případných dalších informací. Framebuffer je v podstatě pravidelná matice fragmentů. Barvy fragmentů tvoří ve framebufferu samostatný color-buffer, který se zobrazuje na obrazovce. Rasterizace je proces, kterým se matematický model plošky (polygonu) převádí na jednotlivé fragmenty. Způsob míchání barvy uložené ve framebufferu a barvy vykreslovaného fragmentu se řídí uživatelem definovanou míchací rovnicí (blending function). V této rovnici vystupují následující členy:

  1. Zdroj (source) je fragment vzniklý rasterizací v právě běžícím rasterizačním procesu.
  2. Cíl (destination) je hodnota zapsaná ve framebufferu, tj. barva fragmentu, který již byl vykreslen dříve. Tato hodnota bude v závislosti na nastavené blending funkci přepsána nebo jinak ovlivněna.

V knihovně OpenGL lze stanovit koeficienty míchání pro každou barevnou složku zvlášť. Tak lze jednoduše dosáhnout na první pohled složitých efektů, například maskování jedné barvy apod.

Obrázek 7: Příklad použití blendingu.

Demonstrační příklady používající blending si popíšeme příště.

13. 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
21_no_z_buffer.py https://github.com/tisnik/presentations/blob/master/pyglet/21_no_z_buffer.py
22_z_buffer.py https://github.com/tisnik/presentations/blob/master/pyglet/22_z_buffer.py

14. Odkazy na Internetu

  1. Pyglet Home Page
    https://bitbucket.org/pyglet/pyglet/wiki/Home
  2. Dokumentace k verzi 1.2
    https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/
  3. Dokumentace k verzi 1.2 ve formátu PDF
    https://readthedocs.org/projects/pyglet/downloads/pdf/pyglet-1.2-maintenance/
  4. PyOpenGL
    http://pyopengl.sourceforge.net/
  5. The #! magic, details about the shebang/hash-bang mechanism on various Unix flavours
    https://www.in-ulm.de/~mascheck/various/shebang/
  6. Shebang (Unix)
    https://en.wikipedia.org/wiki/Shebang_%28Unix%29
  7. Domovská stránka systému LÖVE
    http://love2d.org/
  8. Simple DirectMedia Layer (home page)
    http://www.libsdl.org/
  9. Simple DirectMedia Layer (Wikipedia)
    https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer
  10. Seriál Grafická knihovna OpenGL
    https://www.root.cz/serialy/graficka-knihovna-opengl/
  11. Pyglet event loop
    http://pyglet.readthedocs.io/en/latest/programming_guide/eventloop.html
  12. Decorators I: Introduction to Python Decorators
    http://www.artima.com/weblogs/viewpost.jsp?thread=240808
  13. 3D Programming in Python – Part 1
    https://greendalecs.wordpress.com/2012/04/21/3d-programming-in-python-part-1/
  14. A very basic Pyglet tutorial
    http://www.natan.termitnjak.net/tutorials/pyglet_basic.html