V páté části seriálu o multimediální knihovně Pyglet si ukážeme, jakým způsobem se používá takzvaný „blending“ (míchání) podporovaný grafickými akcelerátory i knihovnou OpenGL. Taktéž se zmíníme o možnostech kombinace blendingu s funkcí paměti hloubky (Z-bufferu).

Obsah

1. Multimediální knihovna Pyglet: blending při kreslení 2D i 3D scén

2. Co je to fragment?

3. Povolení a zákaz blendingu

4. Míchací rovnice použitá při blendingu

5. Specifikace parametrů míchací rovnice funkcemi OpenGL

6. První demonstrační příklad: jednoduchý blending

7. Kombinace funkce blendingu a paměti hloubky

8. Druhý demonstrační příklad: blending + paměť hloubky

9. Otestování více variant míchací funkce

10. Třetí demonstrační příklad: více variant míchací funkce

11. Vliv alfa složky při aplikaci míchací funkce

12. Čtvrtý demonstrační příklad: vliv alfa složky při aplikaci míchací funkce

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

14. Odkazy na Internetu

1. Multimediální knihovna Pyglet: blending při kreslení 2D i 3D scén

V předchozích dílech seriálu o multimediální knihovně Pyglet jsme si již několikrát popisovali barvový formát používaný při práci s barvami v OpenGL (a tím pádem nepřímo používaný i na grafických akcelerátorech). Připomeňme si ve stručnosti, že barvy jsou v tomto barvovém formátu popsány s využitím tří barvových složek označovaných písmeny R (Red), G (Green) a B (Blue). Při použití čísel typu float je hodnota složek v rozsahu 0,0 až 0,1 (včetně obou mezních hodnot), ovšem je možné použít i celočíselné hodnoty s rozsahem 0 až 255. K těmto složkám se často přidává ještě složka čtvrtá, nazývaná A (Alpha). Výsledný barvový model se označuje zkratkou RGBA.

Obrázek 1: Použití blendingu pro vykreslování výbuchů s využitím jednoduchých částicových systémů.

K čemu vlastně může být alfa složka užitečná? Všechny moderní grafické akcelerátory mohou při vykreslování provádět i takzvaný blending neboli míchání (barev). A k tomuto účelu se většinou používá právě již zmíněná čtvrtá barvová složka specifikovaná příkazem glColor4f(R, G, B, A) a nikoli „pouze“ příkazem glColor3f(R, G, B). Složka alfa může na základě nastavené blending funkce (viz další text) představovat například průhlednost specifikované barvy. Při použití blendingu musíme nejdříve nastavit, jakým způsobem se budou kombinovat právě vykreslované fragmenty s hodnotami uloženými ve framebufferu.

Obrázek 2: Použití blendingu pro vykreslování „magických jevů“.

2. Co je to fragment?

Připomeňme si, ž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 (trojúhelníku, čtyřúhelníku, konvexního 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.

Na schématu se zjednodušenou vykreslovací pipeline si povšimněte především toho, že při blendingu je nutné kombinovat již vykreslený obsah framebufferu s novými fragmenty:

Obrázek 3: Klasická OpenGL pipeline se zvýrazněním bloku, v němž se provádí blending. Povšimněte si, že v tomto kroku je zapotřebí číst údaje z framebufferu, tj. údaje o rasterizovaných fragmentech.

3. Povolení a zákaz blendingu

V knihovně OpenGL lze stanovit koeficienty míchání pro každou barvovou složku zvlášť. Tak lze jednoduše dosáhnout na první pohled složitých efektů, například maskování jedné barvy apod. Při spuštění programu, který používá pro vykreslování grafickou knihovnu OpenGL, je vliv alfa složky na vykreslovanou plošku zakázán. Proto pokud chceme programovat některé grafické efekty, musíme před vykreslením vhodně nastavit režim míchání již nakreslené části scény s nově vykreslovanými tělesy. Podobně jako je tomu i v dalších případech se o povolení stará funkce nazvaná glEnable() a o zákaz funkce nazvaná glDisable(). Povolení blendingu tedy může vypadat následovně:

def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)          # barva pozadi obrazku
    glClearDepth(1.0)                         # implicitni hloubka ulozena v pameti hloubky
    ...
    ...
    # další funkce
    ...
    ...
    glEnable(GL_BLEND)                        # povoleni blendingu

Poznámka: blending lze selektivně zapínat či vypínat i přímo při vykreslování, což je ostatně často děje i v praxi.

Obrázek 4: Screenshot demonstračního příkladu, v němž se při vykreslení domečku používá paměť hloubky neboli Z-buffer.

4. Míchací rovnice použitá při blendingu

Míchací rovnici, která se tradičně v knihách o počítačové grafice zapisuje ve vektorovém tvaru, lze rozepsat do čtyř rovnic odpovídajících barvovému modelu RGBA:

Rn = Rs × Sr + Rd × Dr
Gn = Gs × Sg + Gd × Dg
Bn = Bs × Sb + Bd × Db
An = As × Sa + Ad × Da

Význam jednotlivých členů v rovnicích:

Člen Stručný popis
Rn nově vypočtená červená barevná složka
Gn nově vypočtená zelená barevná složka
Bn nově vypočtená modrá barevná složka
An nová hodnota alfa složky
Rs červená barevná složka zdrojového fragmentu
Gs zelená barevná složka zdrojového fragmentu
Bs modrá barevná složka zdrojového fragmentu
As alfa složka zdrojového fragmentu
Rd červená barevná složka cílového fragmentu
Gd zelená barevná složka cílového fragmentu
Bd modrá barevná složka cílového fragmentu
Ad alfa složka cílového fragmentu
Sr míchací faktor pro červenou barvu zdrojového fragmentu
Sg míchací faktor pro zelenou barvu zdrojového fragmentu
Sb míchací faktor pro modrou barvu zdrojového fragmentu
Sa míchací faktor pro alfa složku zdrojového fragmentu
Dr míchací faktor pro červenou barvu cílového fragmentu
Dg míchací faktor pro zelenou barvu cílového fragmentu
Db míchací faktor pro modrou barvu cílového fragmentu
Da míchací faktor pro alfa složku cílového fragmentu

Pro praxi si však stačí zapamatovat význam jednotlivých písmen: R-red, G-green, B-blue, A-alpha, n-new fragment, s-source fragment, d-destination fragment.

5. Specifikace parametrů míchací rovnice funkcemi OpenGL

Ve výše uvedených rovnicích je nutné specifikovat míchací faktory (koeficienty) Sr, Sg, Sb, Sa, Dr, Dg, Db a Da, ostatní hodnoty odpovídají barvám a alfa-složkám zdrojových a cílových fragmentů. Koeficienty S a D se nezadávají přímo číselnou hodnotou, protože se mohou měnit v závislosti na barvách zdrojových a cílových fragmentů. Místo toho se používají symboly, jejichž konkrétní hodnota se vypočte automaticky při rasterizaci.

Pro zadání míchacích koeficientů se používá funkce:

glBlendFunc(sFactor, dFactor)

První parametr sFactor určuje způsob výpočtu míchacích faktorů Sr, Sg, Sb a Sa, druhý parametr dFactor způsob výpočtu faktorů Dr, Dg, Db a Da.

Pro hodnoty, které lze do těchto parametrů dosadit, platí základní jednoduchá pravidla:

  1. Za sFactor popř. dFactor je možné dosadit některou z konstant, které jsou předdefinovány v hlavičkovém souboru gl.h (to samozřejmě platí především pro nativní aplikace psané v C či C++, nicméně podobné konstanty nalezneme i v Pygletu).
  2. Některé z těchto konstant je možné zadat do obou parametrů. Jedná se například o konstanty GL_ZERO, GL_ONE apod. Význam těchto konstant se samozřejmě liší podle toho, do kterého parametru jsou dosazeny.
  3. Některé konstanty lze použít pouze pro parametr sFactor. Jedná se zejména o konstanty GL_DST_COLOR nebo GL_ONE_MINUS_DST_COLOR (v novějších verzích OpenGL již toto omezení neplatí).
  4. Některé konstanty lze naopak použít pouze pro parametr dFactor. Jde o konstanty GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR apod. (opět: v novějších verzích OpenGL již toto omezení neplatí)

V následující tabulce je vypsán význam míchacích faktorů, které můžeme použít pro specifikaci koeficientů míchací rovnice. Ve sloupci Název je uvedeno jméno faktoru, tj. symbolická konstanta definovaná v souboru gl.h popř. modulu Pygletu. Ve sloupci Použití je označeno, zda se může symbolická konstanta použít pro dosazení do parametru sFactor (S), dFactor (D) nebo do obou parametrů. Ve sloupci Význam jsou ve vektorovém tvaru naznačeny hodnoty zdrojových či cílových koeficientů.

Název Použití Význam
GL_ZERO S nebo D (0, 0, 0, 0)
GL_ONE S nebo D (1, 1, 1, 1)
GL_DST_COLOR S (Rd, Gd, Bd, Ad)
GL_SRC_COLOR D (Rs, Gs, Bs, As)
GL_ONE_MINUS_DST_COLOR S (1, 1, 1, 1)-(Rd, Gd, Bd, Ad)
GL_ONE_MINUS_SRC_COLOR D (1, 1, 1, 1)-(Rs, Gs, Bs, As)
GL_SRC_ALPHA S nebo D (As, As, As, As)
GL_ONE_MINUS_SRC_ALPHA S nebo D (1, 1, 1, 1)-(As, As, As, As)
GL_DST_ALPHA S nebo D (Ad, Ad, Ad, Ad)
GL_ONE_MINUS_DST_ALPHA S nebo D (1, 1, 1, 1)-(Ad, Ad, Ad, Ad)
GL_SRC_ALPHA_SATURATE S (f, f, f, 1)   f=min(As, 1-Ad)

Jako příklad na použití blendingu si uvedeme jednoduchý problém. Máme zobrazit dvě plošky přes sebe, ale při překrytí má být bližší ploška průhledná z 50%. Postup řešení tohoto problému je následující:

  1. Blending globálně povolíme příkazem glEnable(GL_BLEN­D).
  2. Nastavíme zdrojový faktor na hodnotu GL_ONE.
  3. Nastavíme cílový faktor na hodnotu GL_ZERO.
  4. Vykreslíme první (spodní) plošku. Tato ploška je vykreslena svou originální barvou, protože cílový fragment (tj. pozadí) je vynulován a barva plošky je vynásobena jedničkou.
  5. Nastavíme zdrojový faktor na hodnotu GL_SRC_ALPHA.
  6. Nastavíme cílový faktor na hodnotu GL_ONE_MINUS_SRC_AL­PHA.
  7. Vykreslíme druhou (vrchní) plošku. Alfa hodnota barvy této plošky musí být nastavena na 0.5. To znamená, že se zdrojový i cílový faktor vynásobí stejnou hodnotou (1=1–0.5) a posléze sečtou. Výsledkem našeho snažení je, že tato druhá ploška je vykreslena s padesátiprocentní průhledností.

6. První demonstrační příklad: jednoduchý blending

V dnešním prvním demonstračním příkladu nejdříve ve funkci init povolíme blending a nastavíme míchání s použitím faktorů GL_SRC_ALPHA a GL_ONE_MINUS_SRC_ALPHA, což znamená, že se každá ploška vykreslí poloprůhledně s ohledem na čtvrtou barvovou složku (alfa). Jedná se o nejběžnější použití blendingu v praxi:

def init():
    glClearColor(0.0, 0.0, 0.3, 0.0)                   # barva pozadi obrazku
    glClearDepth(1.0)                                  # implicitni hloubka ulozena v pameti hloubky
    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
    glEnable(GL_BLEND)                                 # povoleni blendingu
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # blending funkce

Klávesami E a D je možné povolit funkci paměti hloubky, která je ovšem v souvislosti s blendingem problematická – většinou potřebujeme vykreslit všechny plošky, nejenom ty, které jsou nejblíže k pozorovateli. Kurzorovými klávesami je možné otáčet 3D modelem – domečkem:

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

Obrázek 5: Screenshot prvního demonstračního příkladu.

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

#!/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
    glClearDepth(1.0)                         # implicitni hloubka ulozena v pameti hloubky
    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
    glEnable(GL_BLEND)                        # povoleni blendingu
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)



@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
    glColor4f(0.0, 0.0, 1.0, 0.5)                  # 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)

    glColor4f(0.0, 1.0, 0.0, 0.5)             # 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)

    glColor4f(1.0, 0.0, 0.0, 0.5)             # 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)

    glColor4f(1.0, 1.0, 0.0, 0.5)             # 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
    glColor4f(0.0, 1.0, 1.0, 0.5)
    glVertex3f(-5.0,  5.0, -5.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor4f(1.0, 0.0, 1.0, 0.5)
    glVertex3f( 5.0,  5.0, -5.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor4f(1.0, 1.0, 1.0, 0.5)
    glVertex3f( 5.0,  5.0,  5.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor4f(0.0, 0.0, 0.0, 0.5)
    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()

Obrázek 6: Screenshot prvního demonstračního příkladu ve chvíli, kdy je povolena funkce paměti hloubky.

7. Kombinace funkce blendingu a paměti hloubky

Při vykreslování průhledných těles záleží na pořadí vykreslování objektů. Jako první by se proto měly vykreslit nejvzdálenější objekty (resp. přesněji řečeno jejich plošky). Zejména proto, že řazení plošek podle vzdálenosti od pozorovatele je časově náročné a nejednoznačné, je nutno metodu trošku vylepšit. Doporučuje se proto dodržovat následující postup:

  1. Nejprve je zapotřebí při startu aplikace alokovat společně s barvovými buffery i hloubkový buffer (Z-buffer). To je v knihovně Pyglet splněno automaticky.
  2. Hloubkový buffer se musí nastavit do režimu čtení i zápisu (read-write) hloubek fragmentů. Toho dosáhneme zavoláním příkazu glDepthMask(GL_TRUE).
  3. Po nastavení hloubkového bufferu se vykreslí všechny neprůhledné objekty. Tyto objekty se vzhledem k testu hloubky každého vykreslovaného fragmentu vykreslí korektně. To je zapříčiněno funkcí hloubkového bufferu, který pro každý vykreslovaný fragment testuje, zda je umístěn před nebo za již vykresleným fragmentem, jehož hloubka je v hloubkovém bufferu uložena. Po vykreslení všech neprůhledných objektů je tedy v hloubkovém bufferu zapsána „výšková mapa“; nejbližších vykreslených fragmentů v každé buňce framebufferu.
  4. Před vykreslením průhledných objektů se musí hloubkový režim nastavit do režimu read-only, tj. hloubky fragmentů jsou z hloubkového bufferu pouze čteny (a popř. odstraněny z dalšího vykreslování). Zápis hodnot do hloubkového bufferu je zakázán, protože průhledné objekty se musí vykreslit i tehdy, pokud jsou schovány za dalším průhledným objektem a neprůhledné objekty se za objekty průhlednými nesmí vymazat. Nastavení hloubkového bufferu do režimu read-only provedeme příkazem glDepthMask(GL_FALSE).
  5. Dalším krokem je seřazení průhledných objektů podle jejich hloubky (vzdálenosti) od pozorovatele. Objekty se poté vykreslí v tomto pořadí, nejdříve samozřejmě objekt nejvzdálenější.
  6. Složité objekty, tj. objekty, jejichž plošky jsou složeny z mnohoúhelníků, je někdy nutné tesselací rozdělit na jednotlivé trojúhelníky, jinak by mohly nastat vizuální chyby při překrývání objektů. K tesselaci je možné použít funkce z knihovny GLU. U moderních GPU se doporučuje tesselaci provést vždy, neboť se vykreslování urychlí.
  7. Po vykreslení všech průhledných objektů se hloubkový buffer nastaví opět do režimu read-write (glDepthMask(GL_TRUE)), aby se v příštím průchodu neprůhledné objekty vykreslily korektně.

8. Druhý demonstrační příklad: blending + paměť hloubky

Ve druhém demonstračním příkladu je ukázána kombinace použití blendingu a paměti hloubky. Kromě našeho 3D domečku jsou do scény vloženy dvě poloprůhledné stěny. Povšimněte si postupu vykreslování těchto stěn, kdy se nejdříve Z-buffer přepne do režimu čtení a po vykreslení stěn se opět zapne do režimu čtení+zápisu:

def draw_planes():
    glMatrixMode(GL_MODELVIEW)                  # bude se menit modelova matice
    glLoadIdentity()                            # nahrat jednotkovou matici
    glTranslatef(0.0, 0.0, -50.0)               # posun objektu dale od kamery
    glDepthMask(GL_FALSE)                       # Z-buffer v rezimu read-only
    glEnable(GL_BLEND)                          # povoleni michani
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # michaci funkce
    glBegin(GL_QUADS)                           # vykresleni dvou rovin
    glColor4f(1.0, 0.5, 0.5, 0.7) 
    glVertex3f(-14.0, -8.0, 35.0) 
    glVertex3f( -0.0, -8.0, 35.0) 
    glVertex3f( -0.0,  8.0, 35.0) 
    glVertex3f(-14.0,  8.0, 35.0) 
    glColor4f(0.5, 0.5, 1.0, 0.7) 
    glVertex3f(  0.0, -8.0, 25.0) 
    glVertex3f( 14.0, -8.0, 25.0) 
    glVertex3f( 14.0,  8.0, 25.0) 
    glVertex3f(  0.0,  8.0, 25.0) 
    glEnd() 
    glDepthMask(GL_TRUE)                        # Z-buffer v rezimu read-write
    glDisable(GL_BLEND)                         # zakazani michani

Obrázek 7: Screenshot druhého demonstračního příkladu ve chvíli, kdy je povolena funkce paměti hloubky.

#!/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
    glClearDepth(1.0)                         # implicitni hloubka ulozena v pameti hloubky
    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
    glEnable(GL_BLEND)                        # povoleni blendingu
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)



@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
    glColor4f(0.0, 0.0, 1.0, 0.5)                  # 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)

    glColor4f(0.0, 1.0, 0.0, 0.5)             # 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)

    glColor4f(1.0, 0.0, 0.0, 0.5)             # 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)

    glColor4f(1.0, 1.0, 0.0, 0.5)             # 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
    glColor4f(0.0, 1.0, 1.0, 0.5)
    glVertex3f(-5.0,  5.0, -5.0)
    glVertex3f( 5.0,  5.0, -5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor4f(1.0, 0.0, 1.0, 0.5)
    glVertex3f( 5.0,  5.0, -5.0)
    glVertex3f( 5.0,  5.0,  5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor4f(1.0, 1.0, 1.0, 0.5)
    glVertex3f( 5.0,  5.0,  5.0)
    glVertex3f(-5.0,  5.0,  5.0)
    glVertex3f( 0.0, 11.0,  0.0)
 
    glColor4f(0.0, 0.0, 0.0, 0.5)
    glVertex3f(-5.0,  5.0,  5.0)
    glVertex3f(-5.0,  5.0, -5.0)
    glVertex3f( 0.0, 11.0,  0.0)
    glEnd()



def draw_planes():
    glMatrixMode(GL_MODELVIEW)                  # bude se menit modelova matice
    glLoadIdentity()                            # nahrat jednotkovou matici
    glTranslatef(0.0, 0.0, -50.0)               # posun objektu dale od kamery
    glDepthMask(GL_FALSE)                       # Z-buffer v rezimu read-only
    glEnable(GL_BLEND)                          # povoleni michani
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # michaci funkce
    glBegin(GL_QUADS)                           # vykresleni dvou rovin
    glColor4f(1.0, 0.5, 0.5, 0.7) 
    glVertex3f(-14.0, -8.0, 35.0) 
    glVertex3f( -0.0, -8.0, 35.0) 
    glVertex3f( -0.0,  8.0, 35.0) 
    glVertex3f(-14.0,  8.0, 35.0) 
    glColor4f(0.5, 0.5, 1.0, 0.7) 
    glVertex3f(  0.0, -8.0, 25.0) 
    glVertex3f( 14.0, -8.0, 25.0) 
    glVertex3f( 14.0,  8.0, 25.0) 
    glVertex3f(  0.0,  8.0, 25.0) 
    glEnd() 
    glDepthMask(GL_TRUE)                        # Z-buffer v rezimu read-write
    glDisable(GL_BLEND)                         # zakazani michani



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()
    draw_planes()



pyglet.app.run()

Obrázek 8: Screenshot druhého demonstračního příkladu ve chvíli, kdy je vypnuta funkce paměti hloubky.

Obrázek 9: Screenshot druhého demonstračního příkladu ve chvíli, kdy je opět zapnuta funkce paměti hloubky.

9. Otestování více variant míchací funkce

V předchozím demonstračním příkladu jsme si vyzkoušeli pouze jedinou míchací funkci nastavenou s využitím příkazu:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) # michaci funkce

Můžeme si ovšem vyzkoušet i další povolené kombinace. K tomuto účelu si vytvoříme pomocnou funkci, která nastaví míchací funkci a následně do framebufferu vykreslí čtverec žlutou barvou s nastavenou alfa složkou na 0,7. Tato pomocná funkce vypadá následovně:

def draw_square_with_blending(x, y, sfactor, dfactor):
    glBlendFunc(sfactor, dfactor)
    glColor4f(1.0, 1.0, 0.0, 0.7)
    glBegin(GL_QUADS)
    glVertex2f(x, y)
    glVertex2f(x+40, y)
    glVertex2f(x+40, y+40)
    glVertex2f(x, y+40)
    glEnd()

Pozadí scény bude tvořeno šedým čtvercem, při jehož vykreslení bude blending zakázaný:

def draw_background_plane():
    border = 10
    glDisable(GL_BLEND)                       # zakaz blendingu
    glColor3f(0.5, 0.5, 0.5)
    glBegin(GL_QUADS)
    glVertex2f(border, border)
    glVertex2f(window.width-border, border)
    glVertex2f(window.width-border, window.height-border)
    glVertex2f(border, window.height-border)
    glEnd()

Následně si připravíme dva seznamy obsahující konstanty představující typy zdrojových a cílových faktorů:

sfactors = [GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_ZERO, GL_ONE]
dfactors = [GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE]

V posledním kroku ve dvojici vnořených programových smyček zkombinujeme všechny zdrojové faktory se všemi faktory cílovými a vykreslíme žlutý čtverec s povoleným blendingem:

for y in xrange(0, 6):
    sfactor = sfactors[y]
    for x in xrange(0, 6):
        dfactor = dfactors[x]
        draw_square_with_blending( 50+60*x, 50+60*y, sfactor, dfactor)

Výsledkem by měl být následující obrázek:

Obrázek 10: Screenshot třetího demonstračního příkladu.

10. Třetí demonstrační příklad: více variant míchací funkce

Úplný zdrojový kód třetího demonstračního příkladu, který po svém spuštění vykreslil obrázek z desátého screenshotu, vypadá následovně:

#!/usr/bin/env python

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

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


def init():
    glClearColor(0.0, 0.0, 0.0, 0.0)          # barva pozadi obrazku
    glClearDepth(1.0)                         # implicitni hloubka ulozena v pameti hloubky
    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 clear_buffers():
    glClear(GL_COLOR_BUFFER_BIT)              # vymazani vsech bitovych rovin barvoveho bufferu



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



def draw_background_plane():
    border = 10
    glDisable(GL_BLEND)                       # zakaz blendingu
    glColor3f(0.5, 0.5, 0.5)
    glBegin(GL_QUADS)
    glVertex2f(border, border)
    glVertex2f(window.width-border, border)
    glVertex2f(window.width-border, window.height-border)
    glVertex2f(border, window.height-border)
    glEnd()



def draw_square_with_blending(x, y, sfactor, dfactor):
    glBlendFunc(sfactor, dfactor)
    glColor4f(1.0, 1.0, 0.0, 0.7)
    glBegin(GL_QUADS)
    glVertex2f(x, y)
    glVertex2f(x+40, y)
    glVertex2f(x+40, y+40)
    glVertex2f(x, y+40)
    glEnd()



@window.event
def on_draw():
    clear_buffers()
    set_projection()
    draw_background_plane()

    glEnable(GL_BLEND)
    sfactors = [GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_ZERO, GL_ONE]
    dfactors = [GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE]

    for y in xrange(0, 6):
        sfactor = sfactors[y]
        for x in xrange(0, 6):
            dfactor = dfactors[x]
            draw_square_with_blending( 50+60*x, 50+60*y, sfactor, dfactor)



pyglet.app.run()

11. Vliv alfa složky při aplikaci míchací funkce

U některých kombinací zdrojových a cílových faktorů může být zajímavé sledovat, jaký vliv na vykreslení původně žlutého čtverce bude mít proměnlivá hodnota složky alfa, tedy čtvrté barvové složky. To můžeme otestovat, a dokonce velmi snadno. Postačuje nepatrně zmodifikovat demonstrační příklad takovým způsobem, aby se pomocí kurzorových kláves (šipka doleva, šipka doprava) a klávesy R (reset) mohla alfa složka měnit. Pro zajímavost nejsou nikde stanoveny žádné limity, takže alfa složka může být záporná nebo vyšší než 1,0. Realizace změny alfa složky je triviální:

alpha = 0.5
@window.event
def on_draw():
    global alpha
    if keys[key.LEFT]:
        alpha = alpha - 0.01
    if keys[key.RIGHT]:
        alpha = alpha + 0.01
    if keys[key.R]:
        alpha = 0.5

Vykreslovací rutina se taktéž změní, protože se jí předává další parametr – čtvrtá barvová složka:

def draw_square_with_blending(x, y, sfactor, dfactor, alpha):
    glBlendFunc(sfactor, dfactor)
    glColor4f(1.0, 1.0, 0.0, alpha)
    glBegin(GL_QUADS)
    glVertex2f(x, y)
    glVertex2f(x+40, y)
    glVertex2f(x+40, y+40)
    glVertex2f(x, y+40)
    glEnd()

Obrázek 11: Alfa složka žlutých čtverců je nastavena na hodnotu 0,01.

Obrázek 12: Alfa složka žlutých čtverců je nastavena na hodnotu 0,30.

Obrázek 13: Alfa složka žlutých čtverců je nastavena na hodnotu 0,70.

Obrázek 14: Alfa složka žlutých čtverců je nastavena na hodnotu 1,00.

12. Čtvrtý demonstrační příklad: vliv alfa složky při aplikaci míchací funkce

Úplný zdrojový kód čtvrtého a dnes i současně posledního demonstračního příkladu vypadá následovně:

#!/usr/bin/env python

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

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


alpha = 0.5

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



def init():
    glClearColor(0.0, 0.0, 0.0, 0.0)          # barva pozadi obrazku
    glClearDepth(1.0)                         # implicitni hloubka ulozena v pameti hloubky
    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 clear_buffers():
    glClear(GL_COLOR_BUFFER_BIT)              # vymazani vsech bitovych rovin barvoveho bufferu



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



def draw_background_plane():
    border = 10
    glDisable(GL_BLEND)                       # zakaz blendingu
    glColor3f(0.5, 0.5, 0.5)
    glBegin(GL_QUADS)
    glVertex2f(border, border)
    glVertex2f(window.width-border, border)
    glVertex2f(window.width-border, window.height-border)
    glVertex2f(border, window.height-border)
    glEnd()



def draw_square_with_blending(x, y, sfactor, dfactor, alpha):
    glBlendFunc(sfactor, dfactor)
    glColor4f(1.0, 1.0, 0.0, alpha)
    glBegin(GL_QUADS)
    glVertex2f(x, y)
    glVertex2f(x+40, y)
    glVertex2f(x+40, y+40)
    glVertex2f(x, y+40)
    glEnd()



@window.event
def on_draw():
    global alpha

    clear_buffers()
    set_projection()
    draw_background_plane()

    if keys[key.LEFT]:
        alpha = alpha - 0.01
    if keys[key.RIGHT]:
        alpha = alpha + 0.01
    if keys[key.R]:
        alpha = 0.5

    window.set_caption("Alpha = %3.2f" % alpha)
    glEnable(GL_BLEND)
    sfactors = [GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_ZERO, GL_ONE]
    dfactors = [GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_ZERO, GL_ONE]

    for y in xrange(0, 6):
        sfactor = sfactors[y]
        for x in xrange(0, 6):
            dfactor = dfactors[x]
            draw_square_with_blending( 50+60*x, 50+60*y, sfactor, dfactor, alpha)



pyglet.app.run()

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

Všechny čtyři 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
23_blending.py https://github.com/tisnik/presentations/blob/master/pyglet/23_blending.py
24_blending2.py https://github.com/tisnik/presentations/blob/master/pyglet/24_blending2.py
25_blending_function.py https://github.com/tisnik/presentations/blob/master/pyglet/25_blending_function.py
26_variable_alpha.py https://github.com/tisnik/presentations/blob/master/pyglet/26_variable_alpha.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
  15. Alpha blending
    https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending