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
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
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:
- Zdroj (source) je fragment vzniklý rasterizací v právě běžícím rasterizačním procesu.
- 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:
- 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).
- 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.
- 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í).
- 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í:
- Blending globálně povolíme příkazem glEnable(GL_BLEND).
- Nastavíme zdrojový faktor na hodnotu GL_ONE.
- Nastavíme cílový faktor na hodnotu GL_ZERO.
- 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.
- Nastavíme zdrojový faktor na hodnotu GL_SRC_ALPHA.
- Nastavíme cílový faktor na hodnotu GL_ONE_MINUS_SRC_ALPHA.
- 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:
- 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.
- 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).
- 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.
- 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).
- 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ší.
- 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í.
- 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
- Pyglet Home Page
https://bitbucket.org/pyglet/pyglet/wiki/Home - Dokumentace k verzi 1.2
https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/ - Dokumentace k verzi 1.2 ve formátu PDF
https://readthedocs.org/projects/pyglet/downloads/pdf/pyglet-1.2-maintenance/ - PyOpenGL
http://pyopengl.sourceforge.net/ - The #! magic, details about the shebang/hash-bang mechanism on various Unix flavours
https://www.in-ulm.de/~mascheck/various/shebang/ - Shebang (Unix)
https://en.wikipedia.org/wiki/Shebang_%28Unix%29 - Domovská stránka systému LÖVE
http://love2d.org/ - Simple DirectMedia Layer (home page)
http://www.libsdl.org/ - Simple DirectMedia Layer (Wikipedia)
https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer - Seriál Grafická knihovna OpenGL
https://www.root.cz/serialy/graficka-knihovna-opengl/ - Pyglet event loop
http://pyglet.readthedocs.io/en/latest/programming_guide/eventloop.html - Decorators I: Introduction to Python Decorators
http://www.artima.com/weblogs/viewpost.jsp?thread=240808 - 3D Programming in Python - Part 1
https://greendalecs.wordpress.com/2012/04/21/3d-programming-in-python-part-1/ - A very basic Pyglet tutorial
http://www.natan.termitnjak.net/tutorials/pyglet_basic.html - Alpha blending
https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending