Grafická knihovna OpenGL (13): double-buffering

23. 9. 2003
Doba čtení: 7 minut

Sdílet

V tomto dílu seriálu o grafické knihovně OpenGL si ukážeme, jakým způsobem lze zobrazit jednoduchou prostorovou scénu a jak lze změnit pohled na tuto scénu. Také si ukážeme použití takzvaného double-bufferingu, pomocí kterého lze zaručit, že se uživateli celá scéna zobrazí až po vykreslení všech těles bez rušivého problikávání.

Zobrazení prostorové scény s ortografickou kamerou, double-buffering

Při zobrazení prostorové scény musíme vyřešit dva základní problémy. Prvním problémem je zadání údajů o scéně (tj. geometrických a vizuálních údajů o tělesech umístěných v ní), druhým problémem je nastavení kamery, kterou se na scénu díváme, tak, aby byla korektně zobrazena požadovaná část scény.

První problém lze pro jednoduché scény řešit triviálně: tělesa ve scéně rozložíme na jednodušší části, které lze reprezentovat pomocí grafických primitiv. U trojrozměrných scén musíme pro každý vrchol (vertex) grafického primitiva zadat minimálně jeho souřadnice v prostoru (tj. buď trojici [x, y, z], nebo čtveřici [x, y, z, w]), ve skutečnosti však pro většinu těles musíme specifikovat i barvu vrcholu, jeho normálu, souřadnici do textury a/nebo materiál.

Druhý problém, tj. nastavení kamery, pomocí které pozorujeme scénu, lze řešit více způsoby. První způsob spočívá v nastavení transformačních matic Projection a ModelView pomocí už dříve probraných funkcí glRotate(), glScale() a glTranslate*(). Tento způsob má nevýhodu v tom, že nastavení transformačních matic tímto způsobem není příliš intuitivní (jedním z častých problémů při práci s grafickou knihovnou OpenGL je nesprávné nastavení pohledu na zobrazovanou scénu, takže programátor v horším případě uvidí pouze černou obrazovku, protože se kamera dívá mimo scénu někam do nekonečna).

Proto se využívají další funkce pro práci s kamerou. Ve skutečnosti se pomocí těchto funkcí nepřímo nastavují transformační matice, takže výsledný efekt je stejný jako přímé volání funkcí pro změnu transformačních matic (samozřejmě s korektními parametry). Mezi funkce, které umožňují manipulaci s kamerou, patří:

  1. glOrtho(): nastavení ortogonální kamery, tj. takové projekce, u které není zkreslována velikost objektů se vzdáleností od kamery.
  2. glFrustum(): nastavení perspektivní kamery, tj. takové projekce, u které se zdánlivá velikost objektů na obrazovce zmenšuje s rostoucí vzdáleností od kamery.

Kromě těchto základních funkcí můžeme využít i pomocné funkce, které nám nabízí nadstavbová knihovna GLU (OpenGL Utilities). Tyto funkce mají výhodu v tom, že umožňují nastavení kamery pomocí parametrů, které známe i z reálného světa:

  1. gluLookAt(): tato funkce nastaví pozici kamery, směr jejího pohledu a orientaci souřadného systému vůči kameře (směr „stropu“).
  2. gluOrtho2D(): nastavení jednoduché 2D ortografické projekce, tj. pohledu na rovinu X-Y. Blízká a vzdálená ořezávací rovina (viz dále) je nastavena na hodnotu –1 resp. +1.
  3. gluPerspective(): nastavení perspektivní kamery, ale s použitím jiných, intuitivnějších parametrů než u funkce glFrustum().

V tomto dílu si ukážeme použití ortografické kamery. V dalším pokračování budeme mluvit o perspektivní kameře, jejíž nastavení je vzhledem k jejím vlastnostem poněkud složitější.

Ortografická kamera

Termínem ortografická kamera (resp. ortografická projekce) označujeme takovou projekci, u které není zkreslována velikost objektů se vzdáleností od kamery. Ortografická projekce se používá například v různých CAD systémech pro zobrazení součástek nebo v architektonických aplikacích, protože zde lze jednoduše zjistit délky všech hran bez nutnosti složitých přepočtů.

Nastavení ortografické kamery lze provést například pomocí dvojice funkcígluLookAt() a glOrtho(). Pomocí funkce glOrtho() se vhodně nastaví transformační matice Projection tak, že všechna vykreslovaná grafická primitiva jsou ořezána podle šesti ořezávacích rovin. Tyto ořezávací roviny jsou paralelní s osami souřadného systému, vykreslovaný prostor je tedy tvořen osově orientovaným kvádrem (viewing volume). Všechny vrcholy ležící mimo tento kvádr jsou odstraněny.

Tato funkce má deklaraci void glOrtho(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far), kde jednotlivé parametry mají následující význam:

  • left: souřadnice levé ořezávací roviny
  • right: souřadnice pravé ořezávací roviny
  • bottom: souřadnice dolní ořezávací roviny
  • top: souřadnice horní ořezávací roviny
  • near: souřadnice blízké ořezávací roviny
  • far: souřadnice vzdálené ořezávací roviny

Význam jednotlivých ořezávacích rovin a jejich orientace k pozorovateli je naznačena na následujícím obrázku:

Význam jednotlivých ořezávacích rovin a jejich orientace k pozorovateli


Důležité je, že vzdálená a blízká ořezávací rovina jsou zadány ve směru záporné osy z. Pokud je kamera umístěna v počátku (na souřadnicích[0, 0, 0]), mají parametry funkce glOrtho() tento význam:

  • Trojice parametrů [left, bottom, -near] specifikuje bod, který je mapován (transformován) do levého dolního rohu vykreslovaného ok­na.
  • Trojice parametrů [right, top, -near] specifikuje bod, který je mapován (transformován) do pravého horního rohu vykreslovaného ok­na.
  • Parametr -far specifikuje umístění vzdálenější ořezávací roviny.

Jak již bylo napsáno výše, funkcí glOrtho() se mění prvky v Projection matici. Před zavoláním této funkce je tedy nutné nastavit tuto matici jako aktuální a matici samotnou nastavit jako jednotkovou:

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho();

Druhým krokem v nastavení kamery je její umístění a natočení v prostoru. Tato operace se dá provádět přímou změnou matice ModelView, ale pro intuitivnější práci je výhodnější použít funkci gluLookAt(). Tato funkce má deklaraci void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez, GLdouble centerx, GLdouble centery, GLdouble centerz, GLdouble upx, GLdouble upy, GLdouble upz), kde jednotlivé parametry mají následující význam:

  • Parametry eyex, eyey, eyez specifikují souřadnice, na kterých se kamera nachází.
  • Parametry centerx, centery, centerz specifikují souřadnice bodu, na který se kamera dívá. Vzhledem k tomu, že je vypočítán vektor z pozice kamery do zde zadaného bodu, nesmí být tyto body shodné.
  • Parametry upx, upy, upz specifikují vektor směřující „nahoru“ (na vykreslované scéně vertikální směr). Tento vektor nesmí být rovnoběžný s vektorem vytvořeným ze souřadnice pozice kamery a bodu pohledu.

Pomocí těchto devíti parametrů lze vytvořit lokální souřadný systém kamery, který se skládá z vektorů F (forward), L (left) a U (up). Z prvních třech parametrů se vytvoří bod E (pozice kamery), ze druhých třech parametrů bod C (střed pohledu) a z rozdílu souřadnic těchto dvou bodů získáme vektor F (forward). Zbývá nám tedy dopočítat vektory

L a U. Vektor L získáme vektorovým součinem vektorů F a up (poslední tři parametry): L=up x F. Poslední vektor U se získá dalším vektorovým součinem vektorů F a

L: U=F x L. Celá situace je znázorněná na dalším obrázku.

Lokální souřadný systém kamery


Vzhledem k tomu, že pomocí funkce gluLookAt() se mění prvky transformační matice ModelView, je ve většině běžných případů zapotřebí zavolat tuto sekvenci příkazů:

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(eyex, eyey, eyez, centerx, centery,
 centerz, upx, upy, upz);
// další příkazy, které mohou měnit matici ModelView

Double-buffering

Při interaktivní práci s prostorovou scénou je nutné měnit pohled na scénu, tj. bod umístění kamery a další parametry kamery. Po každé změně pohledu se musí celá prostorová scéna znovu překreslit, protože se většinou změní souřadnice všech vrcholů grafických primitiv na obrazovce. Toto překreslení celé scény (které ve většině případů začíná vymazáním plochy obrazovky barvou pozadí) však může nějakou dobu trvat a v této době by uživatel viděl problikávání způsobené postupnou změnou barev pixelů v barvovém bufferu (color bufferu).

Tomuto problikávání lze zabránit tak, že se místo jednoho barvového bufferu použijí buffery dva. Do jednoho z těchto bufferů se vykresluje (tzv. zadní buffer – back buffer) a druhý je zobrazený uživateli (tzv. přední buffer – front buffer). Po dokončení vykreslování do prvního bufferu se tento zobrazí uživateli a role bufferů se obrátí – tomuto využití dvou barvových bufferů se říká double-buffering. Důležité je, že při výměně rolí obou bufferů není zapotřebí provádět žádné přesuny dat v obrazové paměti, protože použití minimálně dvou barvových bufferů je možné na všech grafických akcelerátorech (vyrobených v tomto století).

Princip činnosti single-bufferingu (jeden barvový buffer) a double-bufferingu je zobrazen na následujícím obrázku.

Princip činnosti single-bufferingu a double-bufferingu


Pro úspěšné použití double-bufferingu je zapotřebí při inicializaci grafického kontextu OpenGL (OpenGL rendering context) specifikovat, že se mají vytvořit dva barvové buffery. Toho dosáhneme zavoláním funkce glutInitDispla­yMode() s parametrem nastaveným na GLUT_RGBA|GLU­T_DOUBLE, pomocí něhož se vytvoří dva barvové buffery, z nichž každý používá barevný režim RGB (Red, Green, Blue – viz předchozí díly tohoto seriálu).

Po úspěšném vytvoření dvou barvových bufferů se na konci sekvence příkazů pro vykreslení prostorové scény musí zavolat funkce pro provedení všech příkazů OpenGL a pro prohození rolí obou barvových bufferů:

glFlush();             // provedení všech příkazů OpenGL
glutSwapBuffers();     // prohození rolí obou barvových bufferů

Demonstrační příklady

prvním demonstračním příkladu (zde je zdrojový kód se zvýrazněnou syntaxí) je ukázána základní práce s ortografickou kamerou, tedy nastavení pozice a orientace kamery pomocí funkce gluLookAt(). Na scéně je zobrazeno jednoduché těleso ve tvaru domečku, se kterým lze otáčet pomocí myši se stisknutým tlačítkem. Pro jednoduchost je zobrazen drátový model.

Ve druhém demonstračním příkladu (opět je k dispozici i zdrojový kód se zvýrazněnou syntaxí) je předchozí příklad dále rozpracován – klávesami [0] až [9] lze měnit způsob vykreslení tělesa. Pod tělesem se zobrazuje mřížka, aby bylo i na rychlejších grafických akcelerátorech viditelné překreslování celé scény při rotaci.

bitcoin_skoleni

Ve třetím demonstračním příkladu(zdrojový kód se zvýrazněnou syntaxí) je ukázáno použití metody double-bufferingu. Jedná se o mírně přepracovaný druhý příklad, ve kterém nebyl double buffering použit.

Pro majitele pomalejších linek je zde k dispozici celý článek i s přílohami.

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.