Hlavní navigace

Grafická knihovna OpenGL (14): nastavení perspektivní kamery

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

Sdílet

Dnešní díl seriálu o grafické knihovně OpenGL naváže na díl předchozí, ve kterém jsme popisovali nastavení ortografické kamery a použití double-bufferingu. Dnes si popíšeme způsob nastavení perspektivní kamery. Dále si řekneme, jakým způsobem lze povolit, nastavit a používat paměť hloubky (depth buffer, Z-buffer).

Zobrazení prostorové scény s perspektivní kamerou, paměť hloubky

V předchozí části jsme si ukázali, jakým způsobem můžeme pracovat s ortogonální kamerou. Pro nastavení ortogonální kamery jsme použili dvojici funkcí gluLookAt() a glOrtho() (resp. gluOrtho2D()). Vlastní nastavení prostoru, který je promítán na rovinu obrazovky pomocí ortogonální kamery, bylo poměrně jednoduché, protože stačilo nastavit šest stěn osově orientovaného kvádru, který představuje promítaný prostor.

Nastavení perspektivní kamery je poněkud složitější, protože prostor viditelný touto kamerou nemá obecně tvar hranolu, ale komolého jehlanu. Musíme tedy vhodným způsobem specifikovat parametry tohoto jehlanu. Podobně jako u kamery nastavené do ortogonálního režimu, i zde se ty části těles, které se nachází mimo viditelný prostor, ořezávají.

Základní funkce pro nastavení kamery v perspektivním re­žimu

Základní funkcí pro nastavení perspektivní kamery je funkce glFrustum(), pomocí které se nastavují prvky transformační matice GL_PROJECTION. Tato funkce má deklaraci:

void glFrustum(
    GLdouble left,
    GLdouble right,
    GLdouble bottom,
    GLdouble top,
    GLdouble near,
    GLdouble far
);

Při volání této funkce tedy musíme zadat šest parametrů typu GLdouble s následujícím významem:

  1. Parametr left udává vzdálenost levé ořezávací roviny od počátku. Vzdálenost se udává ve směru osy x.
  2. Parametr right udává vzdálenost pravé ořezávací roviny od počátku. Vzdálenost se opět udává ve směru osy x, podobně jako u předchozího parametru.
  3. Parametr bottom udává vzdálenost spodní ořezávací roviny od počátku. Vzdálenost se udává ve směru osy y.
  4. Parametr top udává vzdálenost horní ořezávací roviny od počátku. Vzdálenost je opět zadána ve směru osy y, podobně jako u předchozího parametru.
  5. Parametrem near se zadává vzdálenost od kamery k bližší ořezávací rovině kolmé na směr promítání. Vzdálenost je tedy zadána ve směru osy z.
  6. Parametrem far se zadává vzdálenost od kamery k vzdálenější ořezávací rovině kolmé na směr promítání. Stejně jako u předchozího parametru, i tato vzdálenost je zadána ve směru osy z.

Význam parametrů této funkce je znázorněn i na prvním obrázku. Výsek viditelného prostoru ve tvaru komolého jehlanu je zadán rozměry menší podstavy (parametry left, right, bottom a top) a vzdáleností obou podstav od kamery. Úhel stěn je možné z těchto hodnot dopočítat.

Kromě této funkce (která nastavuje transformační matici GL_PROJECTION) lze samozřejmě použít i minule probranou funkcigluLookAt(), pomocí které se nastaví prvky transformační matice MODELVIEW. Pro korektní práci je samozřejmě nutné nejdříve nastavit měněnou matici funkcí glMatrixMode() a poté tuto matici nastavit na jednotkovou pomocí funkce glLoadIdentity().

Význam parametrů funkce glFrustum()
Obrázek 1: Význam parametrů funkce glFrustum()

Pokročilejší nastavení kamery v perspektivním režimu

Pomocí funkce glFrustum() lze sice nastavit projekční transformační matici pro kameru v perspektivním režimu, ale zadávání šesti parametrů komolého jehlanu není příliš intuitivní. Proto byla do knihovny GLU (což je, jak již víme, nadstavba nad knihovnou OpenGL) přidána funkce pro nastavení vlastností kamery v perspektivním režimu pomocí parametrů, jejichž význam známe z reálného světa (používají se například při popisu vlastností objektivů). Tato funkce se jmenuje gluPerspective() a má deklaraci:

void gluPerspective(
    GLdouble fovy,
    GLdouble aspect,
    GLdouble near,
    GLdouble far
);

kde jednotlivé parametry mají následující význam:

  1. Parametr fovy určuje zorný úhel (Field Of View) kamery. Tento úhel je zadán v rovině x-z, která prochází počátkem. Běžně používané zorné úhly se pohybují v rozsahu 30–80 stupňů, je však možné zadat i úhly menší (teleobjektiv) nebo větší („rybí oko“).
  2. Parametr aspect udává poměr mezi šířkou a výškou zadávaného jehlanu. Tento parametr lze použít pro roztažení popř. smrsknutí obrázku ve směru jedné ze souřadných os. Hodnotu tohoto parametru můžeme získat podílem šířky a výšky podstavy jehlanu. Většinou se tato hodnota zjišťuje z rozměrů okna pro kreslení.
  3. Parametrem near se zadává vzdálenost od kamery k bližší ořezávací rovině kolmé na směr promítání. Vzdálenost je tedy zadána ve směru osy z. Význam tohoto parametru je tedy stejný jako u funkce glFrustum().
  4. Parametrem far se zadává vzdálenost od kamery k vzdálenější ořezávací rovině kolmé na směr promítání. Stejně jako u předchozího parametru, i tato vzdálenost je zadána ve směru osy z. Význam tohoto parametru je opět totožný s parametrem funkce glFrustum().

Význam jednotlivých parametrů funkce gluPerspective() je vysvětlen i na druhém obrázku. Z tohoto obrázku je také patrné, jak jednotlivé parametry ovlivňují nastavení kamery. Dvojice parametrů fovy a aspect nastavuje tvar menší podstavy komolého jehlanu, zatímco parametry near a far nastavují výšku a velikost jehlanu.

Význam parametrů funkce gluPerspective()
Obrázek 2: Význam parametrů funkce gluPerspective()

Paměť hloubky

Pokud jste si přeložili a vyzkoušeli příklady 1 a 2 z tohoto dílu (nebo všechny příklady z předchozího dílu), pravděpodobně jste zjistili, že se stěny tělesa někdy nepřekrývají správně. Je to způsobeno tím, že stěny (tj. trojúhelníky a čtyřúhelníky) se vykreslují v tom pořadí, v jakém jsou zadány ve zdrojovém souboru. Toto pořadí však neodpovídá reálné viditelnosti jednotlivých stěn (mimo jiné i z důvodu různého natočení scény).

Jedno z řešení viditelnosti by mohlo spočívat v ručním seřazení stěn tak, že nejdříve by se vykreslily stěny nejvzdálenější a poté by se postupně překreslovaly stěny bližší. Toto řešení, které se také nazývá malířův algoritmus, je však zdlouhavé (stěny se musí seřadit), nepodporuje proudové zpracování dat (znovu řazení) a může vést k nejednoznačnostem při vyhodnocování vzdálenosti jednotlivých stěn, například když se tři stěny vzájemně překrývají.

OpenGL je určeno pro proudové zpracování dat, proto byla použita jiná technika řešení viditelnosti. V této technice je použita takzvaná paměť hloubky (depth buffer nebo také Z-buffer). Paměť hloubky obsahuje rastrová data, nejsou zde však uloženy barvy jednotlivých fragmentů, ale jejich kolmé vzdálenosti od projekční roviny (ve skutečnosti se interně ukládají převrácené hodnoty těchto vzdáleností). Před vykreslením barvy fragmentu do barvového bufferu se nejdříve provede několik testů, mezi něž patří i test na vzdálenost (hloubku) fragmentu. Pokud fragment tímto testem projde, může být vykreslen. V opačném případě je z dalšího zpracování vyjmut. Testy a operace s fragmenty jsou naznačeny na třetím obrázku, kde je zvýrazněna část testu hloubky fragmentu s hodnotou uloženou v paměti hloubky.

Testy a operace s fragmenty
Obrázek 3: Testy a operace s fragmenty, test na hloubku fragmentů

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. Toho dosáhneme tak, že do bitových příznaků předávaných do funkce glutInitDispla­yMode() přidáme i příznak GLUT_DEPTH. Narozdí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_DEP­TH_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.
  3. Pomocí funkce glClearDepth(depth) se musí 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.
  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_DEP­TH_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_CO­LOR_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.

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 perspektivní kamerou, tedy nastavení pozice a orientace kamery pomocí funkce gluLookAt() a nastavení režimu kamery pomocí funkce gluPerspective(). Na scéně je zobrazeno jednoduché těleso ve tvaru domečku, se kterým lze otáčet pomocí myši se stisknutým levým tlačítkem. Pomocí pravého tlačítka myši lze objektem pohybovat směrem ke kameře a od kamery. Pro zamezení blikání při překreslování scény je použita metoda double-bufferingu. Klávesou [F] se lze přepnout do tzv. full-screen režimu, kdy je okno aplikace maximalizováno a vykresleno bez okrajů. Klávesou [W] se naopak provádí zmenšení okna na běžnou velikost.

Ve druhém demonstračním příkladu (opět je k dispozici i zdrojový kód se zvýrazněnou syntaxí) je ukázána práce s hloubkovým bufferem. Tento buffer lze zapínat a vypínat pomocí kláves [E]-enable a [D]-disable (ve skutečnosti se nezapíná přímo paměť hloubky, ale povoluje resp. zakazuje se funkce pro testování hloubky fragmentu oproti hloubce uložené v paměti hloubky). Dále je možné pomocí kláves [,] (čárka) a [.] (tečka) měnit zorný úhel kamery. Další funkce (tj. rotace objektem a přibližování) jsou totožné s předchozím příkladem.

root_podpora

Třetí demonstrační příklad (zde se zvýrazněnou syntaxí) představuje základ pro příklady uvedené v dalších částech tohoto seriálu, proto je zapotřebí všechny jeho části důkladně pročíst a pochopit. Pomocí pravého tlačítka myši je možno zvolit operaci, která se bude dále provádět. Pomocí levého tlačítka myši a pohybu myší se potom tato operace (například posun či otočení) realizuje. V příkladu je použito kontextové menu vytvářené pomocí funkcí z knihovny GLUT, jejíž popis jsem už na stránkách Roota zveřejnil.

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

Byl pro vás článek přínosný?

Autor článku

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