Obsah
1. Specifikace perspektivní transformace2. Vlastní implementace funkce gluPerspective()
3. Projekce bodu či vektoru do okna na obrazovce
4. Zpětná projekce ze souřadnic okna do prostoru
5. Demonstrační příklady
6. Obsah dalšího pokračování
7. Seznam funkcí OpenGL a GLUT zmíněných v této části
8. Nové funkce z knihovny GLU popsané v této části
9. Zkomprimovaná verze článku i s přílohami
1. Specifikace perspektivní transformace
V předchozí části tohoto seriálu jsme si popsali funkci, pomocí níž je možné nepřímo zadat projekční matici, která se používá pro specifikaci ortogonálního pohledu na zobrazovanou scénu. Dnes si popíšeme funkci pro jednoduchou specifikaci takzvané perspektivní transformace. Tento typ transformace je možné použít prakticky ve všech aplikacích trojrozměrné počítačové grafiky, protože vcelku věrně napodobuje fungování optického systému lidského oka a jejím výsledkem jsou poměrně realisticky vyhlížející trojrozměrné objekty i celé scény.
Funkce pro jednoduché nastavení perspektivní kamery se v nadstavbové knihovně GLU nazývá (jak jinak) gluPerspective() a má následující hlavičku:
void gluPerspective( GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar );
Význam parametrů této funkce:
- První parametr fovy určuje takzvaný zorný úhel (Field Of View) kamery. Tento úhel je zadán v rovině x-z, která prochází počátkem, tj. bodem O=[0, 0, 0]. Běžně používané zorné úhly se pohybují v rozsahu 30–80 stupňů, je však možné zadat i úhly menší (teleobjektiv, dalekohled) nebo větší (takzvané „rybí oko“).
- Druhý parametr aspect udává poměr mezi šířkou a výškou zadávaného jehlanu, který omezuje zobrazovaný prostor. 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í (v callback funkci onResize()).
- Třetím parametrem zNear 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 (před prvním natočením či posunutím souřadného systému). Význam tohoto parametru je stejný jako u funkce glFrustum() z knihovny OpenGL.
- Posledním parametrem zFar 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().
Obrázek 1: Význam parametrů funkce gluPerspective()
2. Vlastní implementace funkce gluPerspective()
Funkci gluPerspective() lze naprogramovat i s využitím základních funkcí grafické knihovny OpenGL. Příklad implementace této funkce byl převzat ze stránek nehe.gamedev.net, přeložil jsem jej do češtiny (poznámky) a poněkud zjednodušil:
// ---------------------------------------------- // Vlastní implementace funkce gluPerspective() // bez použití nadstavbové knihovny GLU. // ---------------------------------------------- void myGluPerspective( GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar) { // známá konstanta PI je použita pro převody // úhlů mezi stupni a radiány const GLdouble pi=3.1415927; // poloviční vzdálenost x-ové a y-ové // ořezávací roviny GLdouble fw; GLdouble fh; // výpočet vzdálenosti od 0 (počátku souřadnic) // do y-ové ořezávací roviny fh=tan(fovy/180*pi)*zNear/2; // (funkce tan() pracuje s radiány) // výpočet vzdálenosti od 0 (počátku souřadnic) // do x-ové ořezávací roviny fw=fh*aspect; // nakonec zavoláme funkci glFrustum(), která // nastavení transformační matice provede za nás glFrustum(-fw, fw, -fh, fh, zNear, zFar); }
Poloviční vzdálenost od rovin jsme počítali proto, že funkce glFrustum() vyžaduje vzdálenosti zadané od počátku souřadnic, a ne vzájemné vzdálenosti ořezávacích rovin.
Použití perspektivní transformace je ukázáno v prvním a druhém demonstračním příkladu.
3. Projekce bodu či vektoru do okna
Pomocí funkce gluProject() lze provést přímou projekci bodu či vektoru zadaného ve světových souřadnicích (world coordinates) do souřadnic okna (screen coordinates). Tak lze velmi jednoduchým způsobem zjistit, do kterého pixelu na obrazovce se promítne právě zpracovávaný vrchol.
Předností této funkce je, že se neprovádí žádné vykreslování, tj. obsah všech bufferů ve framebufferu zůstane nezměněn, i když je zadaný bod či vektor zpracováván v grafickém vykreslovacím řetězci.
Funkce gluProject() má hlavičku:
GLint gluProject( GLdouble objX, GLdouble objY, GLdouble objZ, const GLdouble *modelViewMatrix, const GLdouble *projectionMatrix, const GLint *viewPort, GLdouble* winX, GLdouble* winY, GLdouble* winZ );
Kde jednotlivé parametry mají následující význam:
- V parametrech objX, objY a objZ je předána pozice bodu ve světových souřadnicích, tj. v prostoru, ve kterém jsou specifikovány vrcholy vykreslovaných těles.
- Pomocí parametru modelViewMatrix se předává ukazatel na pohledovou transformační matici ModelView. Ve většině případů se pracuje s aktuálně nastavenou transformační maticí, ale může se samozřejmě předat jakákoliv jiná matice – jedná se o běžné dvojrozměrné pole o velikosti 4×4 položky typu GLdouble.
- V parametru projectionMatrix se předává ukazatel na projekční transformační matici Projection. Stejně jako u předchozího parametru, i zde může jít buď o aktuálně nastavenou projekční matici, nebo o libovolnou matici, která byla například použita v minulosti (a lze ji tak získat například ze zásobníku matic).
- Pomocí parametru viewPort je předán ukazatel na hodnoty obsahující aktuálně nastavený ViewPort, který mimo jiné zabezpečuje mapování souřadnic po provedení všech předchozích transformací do GUI okna aplikace. Pro nastavení ViewPortu se používá nám známá funkceglViewport(), kterou jsme použili prakticky ve všech předchozích demonstračních příkladech – z těchto příkladů by mělo být použití funkce glViewport() zřejmé.
- Poslední tři parametry winX, winY a winZ obsahují ukazatel na proměnné typu GLdouble, do kterých se uloží vypočtená transformovaná pozice bodu v takzvaných souřadnicích okna (screen coordinates). Přes ukazatele winX a winY se naplní souřadnice přímo v okně, přes ukazatel winZ vzdálenost transformovaného bodu od průmětné roviny – převrácená hodnota této vzdálenosti se ukládá do paměti hloubky (Z-bufferu, depth bufferu).
Pokud přímá projekce pomocí funkce gluProject() proběhne v pořádku, vrátí se hodnota GL_TRUE, v opačném případě se vrátí hodnota GL_FALSE.
4. Zpětná projekce ze souřadnic okna do prostoru
Pomocí funkce gluUnProject() je možné provést zpětnou projekci souřadnic z okna (screen coordinates) do světových souřadnic (world coordinates). Tuto funkci je možné použít například při získání 3D souřadnic kurzoru myši – stačí načíst pozici kurzoru myši na obrazovce, přiřadit mu z-ovou souřadnici a pomocí zpětné projekce zjistit souřadnice kurzoru v prostoru, ve kterém se specifikují souřadnice vrcholů těles.
Po této operaci je možné jednoduše pomocí myši například hýbat s objekty nebo měnit jejich vrcholy či řídící body (u Bézierových ploch a ploch typu NURBS).
Funkce gluUnProject() má následující hlavičku:
GLint gluUnProject( GLdouble winX, GLdouble winY, GLdouble winZ, const GLdouble *modelViewMatrix, const GLdouble *projectionMatrix, const GLint *viewPort, GLdouble* objX, GLdouble* objY, GLdouble* objZ );
Jednotlivé parametry této funkce mají následující význam:
- V parametru winX je předána x-ová souřadnice na obrazovce – může to být například horizontální poloha kurzoru myši.
- V parametru winY je předána y-ová souřadnice na obrazovce. Může se jednat o vertikální polohu kurzoru myši v okně aplikace.
- V parametru winZ je zapotřebí předat z-ovou souřadnici v prostoru obrazovky. Je to z toho důvodu, že se provádí projekce z jednoho 3D prostoru do jiného 3D prostoru, tj. jde o mapování. Tuto z-ovou souřadnici samozřejmě není možné získat přímo ze souřadnic na obrazovce, ale je potřeba si zvolit nějakou vhodnou hodnotu v kontextu vykreslované scény. V některých případech je výhodné přečíst hodnotu uloženou v paměti hloubky (Z-bufferu) na pozici obrazovky, protože tato hodnota odpovídá vzdálenosti fragmentu od kamery (alespoň při korektně nastavené funkci paměti hloubky).
- Pomocí parametru modelViewMatrix se předává ukazatel na ModelView matici. Může jít buď o aktuálně nastavenou matici, nebo o libovolnou jinou transformační matici – v tomto případě však její použití ve funkci gluUnProject() přestává mít smysl. Způsob získání ModelView matice bude popsán v dalším textu.
- Přes parametr projectionMatrix se předává ukazatel na projekční transformační matici Projection.
- Pomocí parametru viewPort je předán aktuálně nastavený ViewPort, který zabezpečuje mapování souřadnic po provedení transformací do okna aplikace. Pro nastavení ViewPortu se používá nám známá funkce glViewport().
- Posledními parametry jsou tři ukazatele na proměnné typu GLdouble: objX, objY a objZ. V těchto parametrech se vrací souřadnice, které jsou zpětně mapovány do „světového“ prostoru, tj. do prostoru, ve kterém se zadávají pozice vrcholů vykreslovaných těles.
Pokud zpětná projekce pomocí funkce gluUnProject() proběhne v pořádku, vrátí se hodnota GL_TRUE, v opačném případě se vrátí hodnota GL_FALSE.
5. Demonstrační příklady
První demonstrační příklad je velmi jednoduchý. Po spuštění se nejprve nastaví transformační matice Projection a ModelView. Projekční matice je nastavena pomocí výše popsané funkce gluPerspective(). Poté se vykreslí trojrozměrný objekt – čajová konvička, přičemž je možné myší měnit parametry kamery, tj. obsah projekční matice.
Zdrojový kód prvního demonstračního příkladu je dostupný zde, jeho HTML verze se zvýrazněním syntaxe zde.
Obrázek 2: Screenshot prvního demonstračního příkladu s pozměněným poměrem šířka/výška pohledu
Druhý demonstrační příklad navazuje na příklad první s tím rozdílem, že se pro nastavení projekční matice Projection nepoužívá funkce gluPerspective(), ale vlastní implementace pomocí funkce myGluPerspective(), jejíž zdrojový kód byl ukázán ve druhé kapitole této části.
Zdrojový kód druhého demonstračního příkladu je dostupný zde, jeho HTML verze se zvýrazněním syntaxe zde.
Obrázek 3: Screenshot druhého demonstračního příkladu s pozměněným poměrem šířka/výška pohledu
6. Obsah dalšího pokračování
V dalším pokračování tohoto seriálu si ukážeme, jakým způsobem je možné zjednodušit výše zmíněnou funkci gluProject(), která pro běžné použití vyžaduje příliš mnoho parametrů. Také si ukážeme použití zpětné transformace bodů ze souřadnic okna do 3D prostoru pomocí funkce gluUnProject() tak, aby nebylo nutné zadávat z-ovou souřadnici pixelu. Dopředu prozradím pouze to, že se při získávání chybějící z-ové souřadnice budou číst informace z framebufferu.
7. Seznam funkcí OpenGL a GLUT zmíněných v této části
glFrustum()glViewport()
8. Nové funkce GLU popsané v této části
gluPerspective()gluProject()
9. Zkomprimovaná verze článku i s přílohami
Zkomprimovaná verze tohoto článku i s přílohami a demonstračními příklady je uložena zde.