Obsah
1. Základní vlastnosti NURB ploch2. Rozdíl mezi NURB křivkami a plochami
3. Postup při vytváření a vykreslování NURB ploch
4. Příklad funkce vykreslující NURB plochu
5. Demonstrační příklady
6. Obsah dalšího pokračování
7. Nové funkce z knihovny GLU popsané v této části
8. Seznam funkcí OpenGL a GLUT zmíněných v této části
9. Zkomprimovaná verze článku i s přílohami
1. Základní vlastnosti NURB ploch
V předchozích dvou dílech seriálu o nadstavbové grafické knihovně GLU jsme se poměrně dopodrobna zabývali tvorbou NURB křivek. NURB plochy představují, zjednodušeně řečeno, rozšíření NURB křivek o jeden další nezávislý parametr, podobně jako jsou Bézierovy plochy „rozšířením“ Bézierových křivek. NURB plochy jsou hned z několika důvodů mnohem populárnější než NURB křivky.
NURB křivky totiž nenalezly v počítačové grafice většího uplatnění, zejména vinou komplikací při zadávání jejich parametrů a také kvůli výpočetní složitosti, která není v mnoha případech adekvátní jejich plánovanému použití. Ve velkém množství aplikací i v systémových knihovnách se stále používají a pravděpodobně i dlouhou dobu budou používat Bézierovy křivky nebo neracionální B-spline křivky (jedná se například o program Corel Draw, Adobe Illustrator, PostScriptové fonty, TrueType fonty aj.).
Při tvorbě animací se naopak používají křivky, u nichž musí být zaručena konstantní rychlost pohybu bodu po nich – příkladem mohou být interpolační křivky typu Kochanek-Bartels a Catmul-Rom spliny, které jsou interně založeny na Bézierových křivkách (i když to při prvním pohledu na jejich vzorce není patrné). NURB křivky se při animacích nepoužívají právě z toho důvodu, že u nich není zaručena konstantní rychlost pohybujícího se předmětu při konstantní změně jejího parametru t o pevný krok dt.
Zcela odlišná je situace u NURB ploch, které jsou naopak široce používané a zejména v oblasti CAD/CAM systémů vyšší kategorie představují standard dodržovaný všemi výrobci. Velká obliba NURB ploch tkví především v jejich schopnosti přesně reprezentovat části kvadrik (zejména koule, válce a kužele), tvořit offsetové plochy a dokonce vytvářet povrchy, které jsou na některých místech hladké (například se spojitostí G2) a někde se naopak ostře lomí – to NURB plochy činí ideálními například při návrhu karoserií automobilů, na kterých také dochází v některých místech k ostrým zlomům.
Do NURB ploch lze také poměrně jednoduchým způsobem „vyřezávat“ různé otvory. Jedná se o takzvaný trimming, při němž je v parametrickém prostoru zadána oblast plochy, která se nemá vykreslovat. Příkladem použití této techniky mohou být tvorby modelů ploch s otvory pro šrouby nebo model, ve kterém je navzájem pevně nebo pohyblivě spojeno více součástek.
Obrázek 1: Základní tělesa (včetně vybraných kvadrik) vytvořená z NURBS
2. Rozdíl mezi NURB křivkami a plochami
NURB křivky i plochy jsou založeny na stejné teorii, ale je samozřejmé, že se od sebe tyto dva matematické objekty v několika směrech odlišují. Pro uživatele je patrně nejnápadnější vizuální vzhled křivek a ploch, pro programátora však existuje odlišností více. Nejvíce patrné je zejména uspořádání řídících bodů NURB plochy. Ty nad plochou (jež může být v některých případech dokonce nespojitá) tvoří jakousi mřížku podobnou té, kterou jsme si ukazovali při popisu Bézierových ploch (viz první ilustrační obrázek), s tím rozdílem, že velikost mřížky může být libovolná a dokonce v každém směru jiná.
Obrázek 2: Šestnáct řídících bodů Bézierova bikubického plátu
V případě NURB křivek musel být kromě souřadnic jednotlivých řídících bodů v prostoru specifikován i uzlový vektor (knot vector), pomocí něhož se původně rovnoměrně rozdělený interval hodnot jednoho parametru t rozdělil na intervaly hodnot o téměř libovolné velikosti. Vzhledem k tomu, že NURB plochy patří do skupiny parametrických ploch, které mají dva nezávislé parametry u a v, je nutné při jejich specifikaci zadávat také dvojici uzlových vektorů, každý uzlový vektor je přitom přiřazen jednomu z parametrů u nebo v.
Jak uvidíme v následující části tohoto seriálu, existuje přímý vztah mezi počtem řídících bodů, stupněm NURB plochy a počtem hodnot v uzlovém vektoru.
3. Postup při vytváření a vykreslování NURB ploch
Při práci s NURB plochami se využívají téměř stejné funkce,jako u NURB křivek, takže celý postup pro jejich vytváření a vykreslování je, až na odlišné pojmenování některých funkcí, obdobný. Lze jej shrnout do několika bodů:
- Vzhledem k tomu, že NURB plochy jsou vykreslovány z grafických primitiv OpenGL, je zapotřebí vhodně nastavit celý vykreslovací řetězec OpenGL tak, aby samotné vykreslování nebylo pro grafický akcelerátor zbytečně složité. Zejména je vhodné uvážit, zda se mají pro každý vypočtený bod na ploše automaticky generovat a normalizovat normálové vektory. Při výpočtu osvětlení je nutné normály vytvářet (povolit funkcí glEnable(GL_AUTO_NORMAL)). V opačném případě je vhodné normalizaci normálových vektorů zakázat pomocí funkceglDisable(GL_AUTO_NORMAL), čímž dojde ke znatelnému urychlení výpočtů.
- Dále se musí vytvořit objekt, který reprezentuje vznikající NURB plochu v operační paměti počítače. Vytvoření tohoto objektu se provede pomocí funkce gluNewNurbsRenderer(). Tato funkce nevyžaduje žádný parametr. V případě úspěšného vytvoření objektu se vrací ukazatel na tento objekt, v případě chyby se vrátí hodnota 0 (tj. NULL).
- Pro NURB plochy je možné nastavit několik atributů, podobně jako u NURB křivek nebo kvadrik. O nastavení všech atributů se postará funkce gluNurbsProperty(). Hodnoty nastavených atributů lze zpětně přečíst pomocí funkce gluGetNurbsProperty().
- Pro registraci callback funkce, jež se volá v případě chyby při renderování NURB ploch, se musí zavolat registrační funkce gluNurbsCallback(), které se předá ukazatel na uživatelem vytvořenou callback funkci. Opětovný zákaz volání callback funkce se provede předáním hodnoty NULL. Je nutné mít na paměti, že kontrola chyb může degradovat rychlost vykreslování NURB křivky, protože některé kontroly jsou výpočetně složité.
- Počátek vytváření NURB plochy zajistí zavolání funkce gluBeginSurface(). Po zavolání této funkce je nutné specifikovat všechny řídící body NURB plochy a samotnou specifikaci uzavřít pomocí funkce gluEndSurface().
- řídící body NURB plochy se specifikují pomocí funkce gluNurbsSurface(). Kromě řídících bodů v 3D prostoru je možné definovat barvy jednotlivých řídících bodů v prostoru RGBA, normálové vektory a v neposlední řadě i souřadnice do textury.
- Konec vytváření NURB plochy je indikován zavoláním funkce gluEndSurface(). Funkce gluBeginSurface() a gluEndSurface() tvoří příkazové závorky a měly by tedy být volány vždy ve dvojicích.
- Podobně jako u NURB křivek, i objekt NURB ploch se ruší zavoláním funkce gluDeleteNurbsRenderer(). Po vyvolání této funkce již není možné s dříve vytvořeným objektem pracovat a každé jeho další použití by mělo skončit s chybou, tj. vyvoláním dříve registrované callback funkce.
4. Příklad funkce vykreslující NURB plochu
Podrobnější popis výše zmíněných funkcí bude uveden v navazujícím pokračování tohoto seriálu, zde si předběžně uvedeme příklad funkce, pomocí níž se vykreslí jednoduchá NURB plocha. V této funkci se nejdříve vytvoří objekt reprezentující NURB plochu. Posléze se nastaví některé základní parametry tohoto objektu a následně se předají souřadnice řídících bodů spolu s příslušnou dvojicí uzlových vektorů. Po těchto operacích, při nichž je křivka vykreslena, je objekt s NURB plochou opět zrušen:
// --------------------------------------------- // Ukázka funkce pro vykreslení NURB plochy // specifikované šestnácti řídícími body a dvěma // shodnými uzlovými vektory. // --------------------------------------------- void renderNURBS(void) { // objekt NURB křivky GLUnurbs *nurbs; // pole s řídícími body GLfloat ctlpoints[4][4][3]; // uzlový vektor GLfloat knots[8]={ 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0 }; // nastavit souřadnice řídících bodů int u,v; for (u=0; u<4; u++) { for (v=0; v<4; v++) { // x-ová souřadnice ctlpoints[u][v][0]=2.0*((GLfloat)u-1.5); // y-ová souřadnice ctlpoints[u][v][1]=2.0*((GLfloat)v-1.5); // čtveřice prostředních řídících // bodů NURB plochy if ((u==1 || u==2)&&(v==1 || v==2)) ctlpoints[u][v][2]=6.0; // okrajové řídící body else ctlpoints[u][v][2]=0.0; } } // vytvoření NURB plochy nurbs=gluNewNurbsRenderer(); // registrace callback funkce gluNurbsCallback(nurbs, GLU_ERROR, onError); // nastavení základních parametrů gluNurbsProperty(nurbs, GLU_SAMPLING_TOLERANCE, 25.0); gluNurbsProperty(nurbs, GLU_DISPLAY_MODE, GLU_FILL); // specifikace řídících bodů a následné // vykreslení NURB plochy gluBeginSurface(nurbs); gluNurbsSurface(nurbs, 8, knots, 8, knots, 4 * 3, 3, &ctlpoints[0][0][0], 4, 4, GL_MAP2_VERTEX_3); // konec specifikace NURB plochy gluEndSurface(nurbs); // odstranění objektu z paměti gluDeleteNurbsRenderer(nurbs); }
Praktické použití výše zmíněných funkcí bude patrné z demonstračních příkladů.
5. Demonstrační příklady
Po překladu a spuštění prvního demonstračního příkladu se zobrazí NURB plocha jako drátový (wireframe) model. NURB plocha je ve směrech obou parametrů u i v stupně tři, a je proto specifikována pomocí šestnácti řídících bodů uspořádaných do mřížky 4×4 body. Pomocí levého tlačítka myši je možné zobrazenou plochou rotovat, pravým tlačítkem se mění vzdálenost tělesa od pozorovatele (a tím samozřejmě i zdánlivá velikost obrazu NURB plochy na obrazovce).
Ihned po vytvoření objektu s NURB plochou je registrována callback funkce, která je zavolána v případě výskytu nějaké chyby při vykreslování NURB. Posléze jsou nastaveny základní vlastnosti NURB plochy, zejména tolerance při výpočtu souřadnic jednotlivých vrcholů na ploše (tím je ovlivněno dělení plochy na rovinné plošky) a také způsob vykreslení plochy, tj. typ grafických primitiv, ze kterých je plocha složena.
Vzhledem k tomu, že je plocha přepočítána při každém překreslení okna ve funkci drawScene(), je při běhu aplikace patrné, jakým způsobem se mění počet grafických primitiv, ze kterých je plocha složena, při jejím přibližování a vzdalování od pozorovatele. Při velkém přiblížení může počet prováděných matematických operací vzrůst natolik, že aplikace přestane být na chvíli ovladatelná, protože jak výpočty, tak i reakce na uživatelovy pokyny jsou pro jednoduchost zpracovávány v jednom vlákně.
Pomocí klávesy F lze provést přepnutí do celého okna, klávesou W se provede nastavení původní velikosti okna, tj. 450×450 pixelů. Běh aplikace se ukončí stisknutím klávesy ESC.
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 3: Screenshot prvního demonstračního příkladu
Druhý demonstrační příklad se podobá příkladu prvnímu, ovšem s tím rozdílem, že se model NURB plochy nevykresluje pomocí úseček, ale je vyplněný, tj. vykreslený pomocí trojúhelníků a čtyřúhelníků. K tomu jsou navíc nastaveny a povoleny světla a materiály – plocha se tedy vykreslí osvětlená. Všimněte si, že v tomto příkladu je nutné povolit automatický přepočet normálových vektorů, jinak by se osvětlení jednotlivých částí plochy neprovedlo korektně (celá plocha by měla na všech svých místech stejnou normálu).
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 4: Screenshot druhého demonstračního příkladu
6. Obsah dalšího pokračování
V dalším pokračování tohoto seriálu si podrobněji popíšeme funkce, které se využívají při práci s NURB plochami.
7. Nové funkce z knihovny GLU popsané v této části
gluNewNurbsRenderer()gluNurbsProperty()
gluGetNurbsProperty()
gluNurbsCallback()
gluBeginSurface()
gluNurbsSurface()
gluEndSurface()
8. Seznam funkcí OpenGL a GLUT zmíněných v této části
glEnable()glDisable()
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.